VVVVVV/desktop_version/src/editor.cpp

5659 lines
179 KiB
C++
Raw Normal View History

#if !defined(NO_CUSTOM_LEVELS)
#define ED_DEFINITION
2020-01-01 21:29:24 +01:00
#include "editor.h"
#include <physfs.h>
#include <stdio.h>
#include <string>
#include <tinyxml2.h>
#include <utf8/unchecked.h>
2020-01-01 21:29:24 +01:00
#include "Entity.h"
#include "Enums.h"
#include "FileSystemUtils.h"
#include "Graphics.h"
2020-01-01 21:29:24 +01:00
#include "KeyPoll.h"
#include "Map.h"
#include "Music.h"
2020-01-01 21:29:24 +01:00
#include "Script.h"
#include "UtilityClass.h"
#include "XMLUtils.h"
2020-01-01 21:29:24 +01:00
#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
#endif
#ifndef _POSIX_SOURCE
#define _POSIX_SOURCE
#endif
2020-06-16 02:31:54 +02:00
#include <inttypes.h>
2020-01-01 21:29:24 +01:00
edlevelclass::edlevelclass()
{
tileset=0;
tilecol=0;
warpdir=0;
platx1=0;
platy1=0;
platx2=320;
platy2=240;
platv=4;
enemyx1=0;
enemyy1=0;
enemyx2=320;
enemyy2=240;
enemytype=0;
directmode=0;
}
editorclass::editorclass()
{
//We create a blank map
SDL_memset(contents, 0, sizeof(contents));
2020-01-01 21:29:24 +01:00
for (size_t i = 0; i < SDL_arraysize(vmult); i++)
2020-01-01 21:29:24 +01:00
{
vmult[i] = i * 40 * maxwidth;
2020-01-01 21:29:24 +01:00
}
reset();
}
// comparison, not case sensitive.
static bool compare_nocase (std::string first, std::string second)
2020-01-01 21:29:24 +01:00
{
unsigned int i=0;
while ( (i<first.length()) && (i<second.length()) )
{
if (SDL_tolower(first[i])<SDL_tolower(second[i]))
2020-01-01 21:29:24 +01:00
return true;
else if (SDL_tolower(first[i])>SDL_tolower(second[i]))
2020-01-01 21:29:24 +01:00
return false;
++i;
}
if (first.length()<second.length())
return true;
else
return false;
}
void editorclass::loadZips()
{
directoryList = FILESYSTEM_getLevelDirFileNames();
bool needsReload = false;
for(size_t i = 0; i < directoryList.size(); i++)
{
if (endsWith(directoryList[i], ".zip")) {
PHYSFS_File* zip = PHYSFS_openRead(directoryList[i].c_str());
if (!PHYSFS_mountHandle(zip, directoryList[i].c_str(), "levels", 1)) {
printf("%s\n", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
} else {
needsReload = true;
}
}
}
if (needsReload) directoryList = FILESYSTEM_getLevelDirFileNames();
}
void replace_all(std::string& str, const std::string& from, const std::string& to)
{
if (from.empty())
{
return;
}
size_t start_pos = 0;
while ((start_pos = str.find(from, start_pos)) != std::string::npos)
{
str.replace(start_pos, from.length(), to);
start_pos += to.length(); //In case `to` contains `from`, like replacing 'x' with 'yx'
}
}
std::string find_tag(const std::string& buf, const std::string& start, const std::string& end)
{
size_t tag = buf.find(start);
if (tag == std::string::npos)
{
//No start tag
return "";
}
size_t tag_start = tag + start.size();
size_t tag_close = buf.find(end, tag_start);
if (tag_close == std::string::npos)
{
//No close tag
return "";
}
size_t tag_len = tag_close - tag_start;
std::string value(buf.substr(tag_start, tag_len));
//Encode special XML entities
replace_all(value, "&quot;", "\"");
replace_all(value, "&amp;", "&");
replace_all(value, "&apos;", "'");
replace_all(value, "&lt;", "<");
replace_all(value, "&gt;", ">");
//Encode general XML entities
size_t start_pos = 0;
while ((start_pos = value.find("&#", start_pos)) != std::string::npos)
{
2020-06-16 02:31:54 +02:00
bool hex = value[start_pos + 2] == 'x';
size_t end = value.find(';', start_pos);
2020-06-16 02:31:54 +02:00
size_t real_start = start_pos + 2 + ((int) hex);
std::string number(value.substr(real_start, end - real_start));
2020-06-16 02:31:54 +02:00
if (!is_positive_num(number, hex))
{
return "";
}
2020-06-16 02:31:54 +02:00
uint32_t character = 0;
if (hex)
{
sscanf(number.c_str(), "%" SCNx32, &character);
}
else
{
sscanf(number.c_str(), "%" SCNu32, &character);
}
uint32_t utf32[] = {character, 0};
std::string utf8;
utf8::unchecked::utf32to8(utf32, utf32 + 1, std::back_inserter(utf8));
value.replace(start_pos, end - start_pos + 1, utf8);
}
return value;
}
#define TAG_FINDER(NAME, TAG) \
std::string NAME(const std::string& buf) \
{ \
return find_tag(buf, "<" TAG ">", "</" TAG ">"); \
}
TAG_FINDER(find_metadata, "MetaData"); //only for checking that it exists
TAG_FINDER(find_creator, "Creator");
TAG_FINDER(find_title, "Title");
TAG_FINDER(find_desc1, "Desc1");
TAG_FINDER(find_desc2, "Desc2");
TAG_FINDER(find_desc3, "Desc3");
TAG_FINDER(find_website, "website");
#undef TAG_FINDER
2020-01-01 21:29:24 +01:00
void editorclass::getDirectoryData()
{
ListOfMetaData.clear();
directoryList.clear();
loadZips();
2020-01-01 21:29:24 +01:00
directoryList = FILESYSTEM_getLevelDirFileNames();
for(size_t i = 0; i < directoryList.size(); i++)
{
LevelMetaData temp;
if (getLevelMetaData( directoryList[i], temp))
{
ListOfMetaData.push_back(temp);
}
}
for(size_t i = 0; i < ListOfMetaData.size(); i++)
{
for(size_t k = 0; k < ListOfMetaData.size(); k++)
{
if(compare_nocase(ListOfMetaData[i].title, ListOfMetaData[k].title ))
{
std::swap(ListOfMetaData[i] , ListOfMetaData[k]);
std::swap(directoryList[i], directoryList[k]);
}
}
}
}
bool editorclass::getLevelMetaData(std::string& _path, LevelMetaData& _data )
{
unsigned char *uMem = NULL;
FILESYSTEM_loadFileToMemory(_path.c_str(), &uMem, NULL, true);
if (uMem == NULL)
2020-01-01 21:29:24 +01:00
{
printf("Level %s not found :(\n", _path.c_str());
return false;
}
std::string buf((char*) uMem);
2020-01-01 21:29:24 +01:00
if (find_metadata(buf) == "")
2020-01-01 21:29:24 +01:00
{
printf("Couldn't load metadata for %s\n", _path.c_str());
return false;
2020-01-01 21:29:24 +01:00
}
_data.creator = find_creator(buf);
_data.title = find_title(buf);
_data.Desc1 = find_desc1(buf);
_data.Desc2 = find_desc2(buf);
_data.Desc3 = find_desc3(buf);
_data.website = find_website(buf);
2020-01-01 21:29:24 +01:00
_data.filename = _path;
return true;
2020-01-01 21:29:24 +01:00
}
void editorclass::reset()
{
version=2; //New smaller format change is 2
mapwidth=5;
mapheight=5;
EditorData::GetInstance().title="Untitled Level";
EditorData::GetInstance().creator="Unknown";
Desc1="";
Desc2="";
Desc3="";
website="";
roomnamehide=0;
zmod=false;
xmod=false;
cmod=false;
vmod=false;
hmod=false;
bmod=false;
2020-01-01 21:29:24 +01:00
spacemod=false;
spacemenu=0;
shiftmenu=false;
shiftkey=false;
saveandquit=false;
note="";
notedelay=0;
oldnotedelay=0;
2020-01-01 21:29:24 +01:00
deletekeyheld=false;
textmod = TEXT_NONE;
2020-01-01 21:29:24 +01:00
titlemod=false;
creatormod=false;
desc1mod=false;
desc2mod=false;
desc3mod=false;
websitemod=false;
settingsmod=false;
warpmod=false; //Two step process
warpent=-1;
boundarymod=0;
boundarytype=0;
boundx1=0;
boundx2=0;
boundy1=0;
boundy2=0;
textent=0;
2020-01-01 21:29:24 +01:00
scripttexttype=0;
drawmode=0;
dmtile=0;
dmtileeditor=0;
entcol=0;
tilex=0;
tiley=0;
levx=0;
levy=0;
keydelay=0;
lclickdelay=0;
savekey=false;
loadkey=false;
updatetiles=true;
changeroom=true;
levmusic=0;
entframe=0;
entframedelay=0;
edentity.clear();
2020-01-01 21:29:24 +01:00
levmusic=0;
for (int j = 0; j < maxheight; j++)
{
for (int i = 0; i < maxwidth; i++)
{
level[i+(j*maxwidth)].tileset=0;
level[i+(j*maxwidth)].tilecol=(i+j)%32;
level[i+(j*maxwidth)].roomname="";
level[i+(j*maxwidth)].warpdir=0;
level[i+(j*maxwidth)].platx1=0;
level[i+(j*maxwidth)].platy1=0;
level[i+(j*maxwidth)].platx2=320;
level[i+(j*maxwidth)].platy2=240;
level[i+(j*maxwidth)].platv=4;
level[i+(j*maxwidth)].enemyx1=0;
level[i+(j*maxwidth)].enemyy1=0;
level[i+(j*maxwidth)].enemyx2=320;
level[i+(j*maxwidth)].enemyy2=240;
level[i+(j*maxwidth)].enemytype=0;
level[i+(j*maxwidth)].directmode=0;
kludgewarpdir[i+(j*maxwidth)]=0;
2020-01-01 21:29:24 +01:00
}
}
Fix X and Y coordinates getting reversed in editorclass::reset() When editorclass::reset() was resetting the contents of the level previously, it was mixing up the X and Y bounds. The Y bound was supposed to be 30*maxheight, and the X bound was supposed to be 40*maxwidth. Instead, it took 30*maxwidth as its Y bound and 40*maxheight as its X bound. Then, when it actually indexes the contents vector to set each tile to 0, it used 30*maxwidth instead of 40*maxwidth. The difference between width and height is a bit hard to spot, but one thing you can do to remember the difference is to remember the fact that X corresponds with width, and Y corresponds with height. Also, rooms are 40 by 30 tiles, and so X (and therefore width) should correspond with 40, and Y (and therefore height) should correspond with 30. As a result of mixing up the variables, whenever you played a 20x20 map, quit the level and then started making a new 20x20 map, the tiles of the last four rows of the previous map would persist, from y=16 (1-indexed) all the way to y=20 (1-indexed). I don't recall anyone ever running into this bug before, which is a bit strange. But if no one truly has ever ran into this bug before, then I'm genuinely surprised. While working on the patch to fix the enemy type room property of each room not getting reset, and testing the fix, I noticed that for some reason some contents of the previous level I played in order to test the enemy type property persisting was ALSO persisting alongside the enemy type property. Then I read the code and when I realized that the X and Y bounds were getting mixed up I groaned. Very loudly.
2020-01-23 10:41:50 +01:00
for (int j = 0; j < 30 * maxheight; j++)
2020-01-01 21:29:24 +01:00
{
Fix X and Y coordinates getting reversed in editorclass::reset() When editorclass::reset() was resetting the contents of the level previously, it was mixing up the X and Y bounds. The Y bound was supposed to be 30*maxheight, and the X bound was supposed to be 40*maxwidth. Instead, it took 30*maxwidth as its Y bound and 40*maxheight as its X bound. Then, when it actually indexes the contents vector to set each tile to 0, it used 30*maxwidth instead of 40*maxwidth. The difference between width and height is a bit hard to spot, but one thing you can do to remember the difference is to remember the fact that X corresponds with width, and Y corresponds with height. Also, rooms are 40 by 30 tiles, and so X (and therefore width) should correspond with 40, and Y (and therefore height) should correspond with 30. As a result of mixing up the variables, whenever you played a 20x20 map, quit the level and then started making a new 20x20 map, the tiles of the last four rows of the previous map would persist, from y=16 (1-indexed) all the way to y=20 (1-indexed). I don't recall anyone ever running into this bug before, which is a bit strange. But if no one truly has ever ran into this bug before, then I'm genuinely surprised. While working on the patch to fix the enemy type room property of each room not getting reset, and testing the fix, I noticed that for some reason some contents of the previous level I played in order to test the enemy type property persisting was ALSO persisting alongside the enemy type property. Then I read the code and when I realized that the X and Y bounds were getting mixed up I groaned. Very loudly.
2020-01-23 10:41:50 +01:00
for (int i = 0; i < 40 * maxwidth; i++)
2020-01-01 21:29:24 +01:00
{
Fix X and Y coordinates getting reversed in editorclass::reset() When editorclass::reset() was resetting the contents of the level previously, it was mixing up the X and Y bounds. The Y bound was supposed to be 30*maxheight, and the X bound was supposed to be 40*maxwidth. Instead, it took 30*maxwidth as its Y bound and 40*maxheight as its X bound. Then, when it actually indexes the contents vector to set each tile to 0, it used 30*maxwidth instead of 40*maxwidth. The difference between width and height is a bit hard to spot, but one thing you can do to remember the difference is to remember the fact that X corresponds with width, and Y corresponds with height. Also, rooms are 40 by 30 tiles, and so X (and therefore width) should correspond with 40, and Y (and therefore height) should correspond with 30. As a result of mixing up the variables, whenever you played a 20x20 map, quit the level and then started making a new 20x20 map, the tiles of the last four rows of the previous map would persist, from y=16 (1-indexed) all the way to y=20 (1-indexed). I don't recall anyone ever running into this bug before, which is a bit strange. But if no one truly has ever ran into this bug before, then I'm genuinely surprised. While working on the patch to fix the enemy type room property of each room not getting reset, and testing the fix, I noticed that for some reason some contents of the previous level I played in order to test the enemy type property persisting was ALSO persisting alongside the enemy type property. Then I read the code and when I realized that the X and Y bounds were getting mixed up I groaned. Very loudly.
2020-01-23 10:41:50 +01:00
contents[i+(j*40*maxwidth)]=0;
2020-01-01 21:29:24 +01:00
}
}
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
hooklist.clear();
2020-01-01 21:29:24 +01:00
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
sb.clear();
2020-01-01 21:29:24 +01:00
clearscriptbuffer();
sbx=0;
sby=0;
pagey=0;
scripteditmod=false;
sbscript="null";
scripthelppage=0;
scripthelppagedelay=0;
hookmenupage=0;
hookmenu=0;
script.customscripts.clear();
2020-02-11 06:34:01 +01:00
returneditoralpha = 0;
oldreturneditoralpha = 0;
ghosts.clear();
2020-06-13 00:27:21 +02:00
currentghosts = 0;
onewaycol_override = false;
loaded_filepath = "";
2020-01-01 21:29:24 +01:00
}
void editorclass::gethooks()
{
//Scan through the script and create a hooks list based on it
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
hooklist.clear();
for (size_t i = 0; i < script.customscripts.size(); i++)
2020-01-01 21:29:24 +01:00
{
Script& script_ = script.customscripts[i];
hooklist.push_back(script_.name);
2020-01-01 21:29:24 +01:00
}
}
void editorclass::loadhookineditor(std::string t)
{
//Find hook t in the scriptclass, then load it into the editor
clearscriptbuffer();
for(size_t i = 0; i < script.customscripts.size(); i++)
2020-01-01 21:29:24 +01:00
{
Script& script_ = script.customscripts[i];
if(script_.name == t)
2020-01-01 21:29:24 +01:00
{
sb = script_.contents;
break;
2020-01-01 21:29:24 +01:00
}
}
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
if(sb.empty())
{
//Always have one line or we'll have problems
sb.resize(1);
}
2020-01-01 21:29:24 +01:00
}
void editorclass::addhooktoscript(std::string t)
{
//Adds hook+the scriptbuffer to the end of the scriptclass
removehookfromscript(t);
Script script_;
script_.name = t;
script_.contents = sb;
script.customscripts.push_back(script_);
2020-01-01 21:29:24 +01:00
}
void editorclass::removehookfromscript(std::string t)
{
//Find hook t in the scriptclass, then removes it (and any other code with it)
for (size_t i = 0; i < script.customscripts.size(); i++)
2020-01-01 21:29:24 +01:00
{
Script& script_ = script.customscripts[i];
2020-01-01 21:29:24 +01:00
if (script_.name == t)
2020-01-01 21:29:24 +01:00
{
script.customscripts.erase(script.customscripts.begin() + i);
break;
2020-01-01 21:29:24 +01:00
}
}
}
void editorclass::removehook(std::string t)
{
//Check the hooklist for the hook t. If it's there, remove it from here and the script
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
removehookfromscript(t);
hooklist.erase(std::remove(hooklist.begin(), hooklist.end(), t), hooklist.end());
2020-01-01 21:29:24 +01:00
}
void editorclass::addhook(std::string t)
{
//Add an empty function to the list in both editor and script
removehook(t);
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
hooklist.push_back(t);
2020-01-01 21:29:24 +01:00
addhooktoscript(t);
}
bool editorclass::checkhook(std::string t)
{
//returns true if hook t already is in the list
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
for(size_t i=0; i<hooklist.size(); i++)
2020-01-01 21:29:24 +01:00
{
if(hooklist[i]==t) return true;
}
return false;
}
void editorclass::clearscriptbuffer()
{
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
sb.clear();
2020-01-01 21:29:24 +01:00
}
void editorclass::removeline(int t)
{
//Remove line t from the script
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
if((int)sb.size()>1)
2020-01-01 21:29:24 +01:00
{
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
sb.erase(sb.begin() + t);
2020-01-01 21:29:24 +01:00
}
}
void editorclass::insertline(int t)
{
//insert a blank line into script at line t
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
sb.insert(sb.begin() + t, "");
2020-01-01 21:29:24 +01:00
}
void editorclass::getlin(const enum textmode mode, const std::string& prompt, std::string* ptr)
{
textmod = mode;
textptr = ptr;
textdesc = prompt;
key.enabletextentry();
if (ptr)
{
key.keybuffer = *ptr;
}
else
{
key.keybuffer = "";
textptr = &(key.keybuffer);
}
oldenttext = key.keybuffer;
}
const short* editorclass::loadlevel( int rxi, int ryi )
2020-01-01 21:29:24 +01:00
{
//Set up our buffer array to be picked up by mapclass
rxi -= 100;
ryi -= 100;
if(rxi<0)rxi+=mapwidth;
if(ryi<0)ryi+=mapheight;
if(rxi>=mapwidth)rxi-=mapwidth;
if(ryi>=mapheight)ryi-=mapheight;
static short result[1200];
2020-01-01 21:29:24 +01:00
for (int j = 0; j < 30; j++)
{
for (int i = 0; i < 40; i++)
{
result[i + j*40] = contents[i+(rxi*40)+vmult[j+(ryi*30)]];
2020-01-01 21:29:24 +01:00
}
}
return result;
2020-01-01 21:29:24 +01:00
}
int editorclass::getlevelcol(int t)
{
if(level[t].tileset==0) //Space Station
{
return level[t].tilecol;
}
else if(level[t].tileset==1) //Outside
{
return 32+level[t].tilecol;
}
else if(level[t].tileset==2) //Lab
{
return 40+level[t].tilecol;
}
else if(level[t].tileset==3) //Warp Zone
{
return 46+level[t].tilecol;
}
else if(level[t].tileset==4) //Ship
{
return 52+level[t].tilecol;
}
return 0;
}
int editorclass::getenemycol(int t)
{
switch(t)
{
//RED
case 3:
case 7:
case 12:
case 23:
case 28:
case 34:
case 42:
case 48:
case 58:
return 6;
break;
//GREEN
case 5:
case 9:
case 22:
case 25:
case 29:
case 31:
case 38:
case 46:
case 52:
case 53:
return 7;
break;
//BLUE
case 1:
case 6:
case 14:
case 27:
case 33:
case 44:
case 50:
case 57:
return 12;
break;
//YELLOW
case 4:
case 17:
case 24:
case 30:
case 37:
case 45:
case 51:
case 55:
return 9;
break;
//PURPLE
case 2:
case 11:
case 15:
case 19:
case 32:
case 36:
case 49:
return 20;
break;
//CYAN
case 8:
case 10:
case 13:
case 18:
case 26:
case 35:
case 41:
case 47:
case 54:
return 11;
break;
//PINK
case 16:
case 20:
case 39:
case 43:
case 56:
return 8;
break;
//ORANGE
case 21:
case 40:
return 17;
break;
default:
return 6;
break;
}
return 0;
}
int editorclass::getwarpbackground(int rx, int ry)
{
int tmp=rx+(maxwidth*ry);
switch(level[tmp].tileset)
{
case 0: //Space Station
switch(level[tmp].tilecol)
{
case 0:
return 3;
break;
case 1:
return 2;
break;
case 2:
return 1;
break;
case 3:
return 4;
break;
case 4:
return 5;
break;
case 5:
return 3;
break;
case 6:
return 1;
break;
case 7:
return 0;
break;
case 8:
return 5;
break;
case 9:
return 0;
break;
case 10:
return 2;
break;
case 11:
return 1;
break;
case 12:
return 5;
break;
case 13:
return 0;
break;
case 14:
return 3;
break;
case 15:
return 2;
break;
case 16:
return 4;
break;
case 17:
return 0;
break;
case 18:
return 3;
break;
case 19:
return 1;
break;
case 20:
return 4;
break;
case 21:
return 5;
break;
case 22:
return 1;
break;
case 23:
return 4;
break;
case 24:
return 5;
break;
case 25:
return 0;
break;
case 26:
return 3;
break;
case 27:
return 1;
break;
case 28:
return 5;
break;
case 29:
return 4;
break;
case 30:
return 5;
break;
case 31:
return 2;
break;
default:
return 6;
break;
}
break;
case 1: //Outside
switch(level[tmp].tilecol)
{
case 0:
return 3;
break;
case 1:
return 1;
break;
case 2:
return 0;
break;
case 3:
return 2;
break;
case 4:
return 4;
break;
case 5:
return 5;
break;
case 6:
return 2;
break;
case 7:
return 4;
break;
default:
return 6;
break;
}
break;
case 2: //Lab
switch(level[tmp].tilecol)
{
case 0:
return 0;
break;
case 1:
return 1;
break;
case 2:
return 2;
break;
case 3:
return 3;
break;
case 4:
return 4;
break;
case 5:
return 5;
break;
case 6:
return 6;
break;
default:
return 6;
break;
}
break;
case 3: //Warp Zone
switch(level[tmp].tilecol)
{
case 0:
return 0;
break;
case 1:
return 1;
break;
case 2:
return 2;
break;
case 3:
return 3;
break;
case 4:
return 4;
break;
case 5:
return 5;
break;
case 6:
return 6;
break;
default:
return 6;
break;
}
break;
case 4: //Ship
switch(level[tmp].tilecol)
{
case 0:
return 5;
break;
case 1:
return 0;
break;
case 2:
return 4;
break;
case 3:
return 2;
break;
case 4:
return 3;
break;
case 5:
return 1;
break;
case 6:
return 6;
break;
default:
return 6;
break;
}
break;
case 5: //Tower
return 6;
break;
default:
return 6;
break;
}
}
int editorclass::getenemyframe(int t)
{
switch(t)
{
case 0:
return 78;
break;
case 1:
return 88;
break;
case 2:
return 36;
break;
case 3:
return 164;
break;
case 4:
return 68;
break;
case 5:
return 48;
break;
case 6:
return 176;
break;
case 7:
return 168;
break;
case 8:
return 112;
break;
case 9:
return 114;
break;
default:
return 78;
break;
}
return 78;
}
void editorclass::placetilelocal( int x, int y, int t )
{
if(x>=0 && y>=0 && x<40 && y<30)
{
contents[x+(levx*40)+vmult[y+(levy*30)]]=t;
}
updatetiles=true;
}
int editorclass::base( int x, int y )
{
//Return the base tile for the given tileset and colour
int temp=x+(y*maxwidth);
2020-01-01 21:29:24 +01:00
if(level[temp].tileset==0) //Space Station
{
if(level[temp].tilecol>=22)
{
return 483 + ((level[temp].tilecol-22)*3);
}
else if(level[temp].tilecol>=11)
{
return 283 + ((level[temp].tilecol-11)*3);
}
else
{
return 83 + (level[temp].tilecol*3);
}
}
else if(level[temp].tileset==1) //Outside
{
return 480 + (level[temp].tilecol*3);
}
else if(level[temp].tileset==2) //Lab
{
return 280 + (level[temp].tilecol*3);
}
else if(level[temp].tileset==3) //Warp Zone/Intermission
{
return 80 + (level[temp].tilecol*3);
}
else if(level[temp].tileset==4) //SHIP
{
return 101 + (level[temp].tilecol*3);
}
return 0;
}
int editorclass::backbase( int x, int y )
{
//Return the base tile for the background of the given tileset and colour
int temp=x+(y*maxwidth);
2020-01-01 21:29:24 +01:00
if(level[temp].tileset==0) //Space Station
{
//Pick depending on tilecol
switch(level[temp].tilecol)
{
case 0:
case 5:
case 26:
return 680; //Blue
break;
case 3:
case 16:
case 23:
return 683; //Yellow
break;
case 9:
case 12:
case 21:
return 686; //Greeny Cyan
break;
case 4:
case 8:
case 24:
case 28:
case 30:
return 689; //Green
break;
case 20:
case 29:
return 692; //Orange
break;
case 2:
case 6:
case 11:
case 22:
case 27:
return 695; //Red
break;
case 1:
case 10:
case 15:
case 19:
case 31:
return 698; //Pink
break;
case 14:
case 18:
return 701; //Dark Blue
break;
case 7:
case 13:
case 17:
case 25:
return 704; //Cyan
break;
default:
return 680;
break;
}
}
else if(level[temp].tileset==1) //outside
{
return 680 + (level[temp].tilecol*3);
}
else if(level[temp].tileset==2) //Lab
{
return 0;
}
else if(level[temp].tileset==3) //Warp Zone/Intermission
{
return 120 + (level[temp].tilecol*3);
}
else if(level[temp].tileset==4) //SHIP
{
return 741 + (level[temp].tilecol*3);
}
return 0;
}
int editorclass::at( int x, int y )
{
if(x<0) return at(0,y);
if(y<0) return at(x,0);
if(x>=40) return at(39,y);
if(y>=30) return at(x,29);
if(x>=0 && y>=0 && x<40 && y<30)
{
return contents[x+(levx*40)+vmult[y+(levy*30)]];
}
return 0;
}
int editorclass::freewrap( int x, int y )
{
if(x<0) return freewrap(x+(mapwidth*40),y);
if(y<0) return freewrap(x,y+(mapheight*30));
if(x>=(mapwidth*40)) return freewrap(x-(mapwidth*40),y);
if(y>=(mapheight*30)) return freewrap(x,y-(mapheight*30));
if(x>=0 && y>=0 && x<(mapwidth*40) && y<(mapheight*30))
{
if(contents[x+vmult[y]]==0)
{
return 0;
}
else
{
if(contents[x+vmult[y]]>=2 && contents[x+vmult[y]]<80)
{
return 0;
}
if(contents[x+vmult[y]]>=680)
{
return 0;
}
}
}
return 1;
}
int editorclass::backonlyfree( int x, int y )
{
//Returns 1 if tile is a background tile, 0 otherwise
if(x<0) return backonlyfree(0,y);
if(y<0) return backonlyfree(x,0);
if(x>=40) return backonlyfree(39,y);
if(y>=30) return backonlyfree(x,29);
if(x>=0 && y>=0 && x<40 && y<30)
{
if(contents[x+(levx*40)+vmult[y+(levy*30)]]>=680)
{
return 1;
}
}
return 0;
}
int editorclass::backfree( int x, int y )
{
//Returns 0 if tile is not a block or background tile, 1 otherwise
if(x<0) return backfree(0,y);
if(y<0) return backfree(x,0);
if(x>=40) return backfree(39,y);
if(y>=30) return backfree(x,29);
if(x>=0 && y>=0 && x<40 && y<30)
{
if(contents[x+(levx*40)+vmult[y+(levy*30)]]==0)
{
return 0;
}
}
return 1;
}
int editorclass::spikefree( int x, int y )
{
//Returns 0 if tile is not a block or spike, 1 otherwise
if(x==-1) return free(0,y);
if(y==-1) return free(x,0);
if(x==40) return free(39,y);
if(y==30) return free(x,29);
if(x>=0 && y>=0 && x<40 && y<30)
{
if(contents[x+(levx*40)+vmult[y+(levy*30)]]==0)
{
return 0;
}
else
{
if(contents[x+(levx*40)+vmult[y+(levy*30)]]>=680)
{
return 0;
}
}
}
return 1;
}
int editorclass::free( int x, int y )
{
//Returns 0 if tile is not a block, 1 otherwise
if(x==-1) return free(0,y);
if(y==-1) return free(x,0);
if(x==40) return free(39,y);
if(y==30) return free(x,29);
if(x>=0 && y>=0 && x<40 && y<30)
{
if(contents[x+(levx*40)+vmult[y+(levy*30)]]==0)
{
return 0;
}
else
{
if(contents[x+(levx*40)+vmult[y+(levy*30)]]>=2 && contents[x+(levx*40)+vmult[y+(levy*30)]]<80)
{
return 0;
}
if(contents[x+(levx*40)+vmult[y+(levy*30)]]>=680)
{
return 0;
}
}
}
return 1;
}
int editorclass::absfree( int x, int y )
{
//Returns 0 if tile is not a block, 1 otherwise, abs on grid
if(x>=0 && y>=0 && x<mapwidth*40 && y<mapheight*30)
{
if(contents[x+vmult[y]]==0)
{
return 0;
}
else
{
if(contents[x+vmult[y]]>=2 && contents[x+vmult[y]]<80)
{
return 0;
}
if(contents[x+vmult[y]]>=680)
{
return 0;
}
}
}
return 1;
}
int editorclass::match( int x, int y )
{
if(free(x-1,y)==0 && free(x,y-1)==0 && free(x+1,y)==0 && free(x,y+1)==0) return 0;
if(free(x-1,y)==0 && free(x,y-1)==0) return 10;
if(free(x+1,y)==0 && free(x,y-1)==0) return 11;
if(free(x-1,y)==0 && free(x,y+1)==0) return 12;
if(free(x+1,y)==0 && free(x,y+1)==0) return 13;
if(free(x,y-1)==0) return 1;
if(free(x-1,y)==0) return 2;
if(free(x,y+1)==0) return 3;
if(free(x+1,y)==0) return 4;
if(free(x-1,y-1)==0) return 5;
if(free(x+1,y-1)==0) return 6;
if(free(x-1,y+1)==0) return 7;
if(free(x+1,y+1)==0) return 8;
return 0;
}
int editorclass::outsidematch( int x, int y )
{
if(backonlyfree(x-1,y)==0 && backonlyfree(x+1,y)==0) return 2;
if(backonlyfree(x,y-1)==0 && backonlyfree(x,y+1)==0) return 1;
return 0;
}
int editorclass::backmatch( int x, int y )
{
//Returns the first position match for a border
// 5 1 6
// 2 X 4
// 7 3 8
if(backfree(x-1,y)==0 && backfree(x,y-1)==0 && backfree(x+1,y)==0 && backfree(x,y+1)==0) return 0;
if(backfree(x-1,y)==0 && backfree(x,y-1)==0) return 10;
if(backfree(x+1,y)==0 && backfree(x,y-1)==0) return 11;
if(backfree(x-1,y)==0 && backfree(x,y+1)==0) return 12;
if(backfree(x+1,y)==0 && backfree(x,y+1)==0) return 13;
if(backfree(x,y-1)==0) return 1;
if(backfree(x-1,y)==0) return 2;
if(backfree(x,y+1)==0) return 3;
if(backfree(x+1,y)==0) return 4;
if(backfree(x-1,y-1)==0) return 5;
if(backfree(x+1,y-1)==0) return 6;
if(backfree(x-1,y+1)==0) return 7;
if(backfree(x+1,y+1)==0) return 8;
return 0;
}
int editorclass::edgetile( int x, int y )
{
switch(match(x,y))
{
case 14:
return 0;
break;
case 10:
return 80;
break;
case 11:
return 82;
break;
case 12:
return 160;
break;
case 13:
return 162;
break;
case 1:
return 81;
break;
case 2:
return 120;
break;
case 3:
return 161;
break;
case 4:
return 122;
break;
case 5:
return 42;
break;
case 6:
return 41;
break;
case 7:
return 2;
break;
case 8:
return 1;
break;
case 0:
default:
return 0;
break;
}
return 0;
}
int editorclass::outsideedgetile( int x, int y )
{
switch(outsidematch(x,y))
{
case 2:
return 0;
break;
case 1:
return 1;
break;
case 0:
default:
return 2;
break;
}
return 2;
}
int editorclass::backedgetile( int x, int y )
{
switch(backmatch(x,y))
{
case 14:
return 0;
break;
case 10:
return 80;
break;
case 11:
return 82;
break;
case 12:
return 160;
break;
case 13:
return 162;
break;
case 1:
return 81;
break;
case 2:
return 120;
break;
case 3:
return 161;
break;
case 4:
return 122;
break;
case 5:
return 42;
break;
case 6:
return 41;
break;
case 7:
return 2;
break;
case 8:
return 1;
break;
case 0:
default:
return 0;
break;
}
return 0;
}
int editorclass::labspikedir( int x, int y, int t )
{
// a slightly more tricky case
if(free(x,y+1)==1) return 63 + (t*2);
if(free(x,y-1)==1) return 64 + (t*2);
if(free(x-1,y)==1) return 51 + (t*2);
if(free(x+1,y)==1) return 52 + (t*2);
return 63 + (t*2);
}
int editorclass::spikedir( int x, int y )
{
if(free(x,y+1)==1) return 8;
if(free(x,y-1)==1) return 9;
if(free(x-1,y)==1) return 49;
if(free(x+1,y)==1) return 50;
return 8;
}
void editorclass::findstartpoint()
2020-01-01 21:29:24 +01:00
{
//Ok! Scan the room for the closest checkpoint
int testeditor=-1;
//First up; is there a start point on this screen?
for(size_t i=0; i<edentity.size(); i++)
2020-01-01 21:29:24 +01:00
{
//if() on screen
if(edentity[i].t==16 && testeditor==-1)
{
testeditor=i;
}
}
if(testeditor==-1)
{
game.edsavex = 160;
game.edsavey = 120;
game.edsaverx = 100;
game.edsavery = 100;
game.edsavegc = 0;
game.edsavey--;
game.edsavedir=1;
2020-01-01 21:29:24 +01:00
}
else
{
//Start point spawn
int tx=(edentity[testeditor].x-(edentity[testeditor].x%40))/40;
int ty=(edentity[testeditor].y-(edentity[testeditor].y%30))/30;
game.edsavex = ((edentity[testeditor].x%40)*8)-4;
game.edsavey = (edentity[testeditor].y%30)*8;
game.edsaverx = 100+tx;
game.edsavery = 100+ty;
game.edsavegc = 0;
game.edsavey++;
2020-01-01 21:29:24 +01:00
game.edsavedir=1-edentity[testeditor].p1;
}
}
int editorclass::findtrinket(int t)
{
int ttrinket=0;
for(int i=0; i<(int)edentity.size(); i++)
2020-01-01 21:29:24 +01:00
{
if(i==t) return ttrinket;
if(edentity[i].t==9) ttrinket++;
}
return 0;
}
int editorclass::findcrewmate(int t)
{
int ttrinket=0;
for(int i=0; i<(int)edentity.size(); i++)
2020-01-01 21:29:24 +01:00
{
if(i==t) return ttrinket;
if(edentity[i].t==15) ttrinket++;
}
return 0;
}
int editorclass::findwarptoken(int t)
{
int ttrinket=0;
for(int i=0; i<(int)edentity.size(); i++)
2020-01-01 21:29:24 +01:00
{
if(i==t) return ttrinket;
if(edentity[i].t==13) ttrinket++;
}
return 0;
}
void editorclass::switch_tileset(const bool reversed /*= false*/)
{
const char* tilesets[] = {"Space Station", "Outside", "Lab", "Warp Zone", "Ship"};
const size_t roomnum = levx + levy*maxwidth;
if (!INBOUNDS_ARR(roomnum, level))
{
return;
}
edlevelclass& room = level[roomnum];
int tiles = room.tileset;
if (reversed)
{
tiles--;
}
else
{
tiles++;
}
const int modulus = SDL_arraysize(tilesets);
tiles = (tiles % modulus + modulus) % modulus;
room.tileset = tiles;
clamp_tilecol(levx, levy);
char buffer[64];
SDL_snprintf(buffer, sizeof(buffer), "Now using %s Tileset", tilesets[tiles]);
note = buffer;
notedelay = 45;
updatetiles = true;
}
void editorclass::switch_tilecol(const bool reversed /*= false*/)
{
const size_t roomnum = levx + levy*maxwidth;
if (!INBOUNDS_ARR(roomnum, level))
{
return;
}
edlevelclass& room = level[roomnum];
if (reversed)
{
room.tilecol--;
}
else
{
room.tilecol++;
}
clamp_tilecol(levx, levy, true);
notedelay = 45;
note = "Tileset Colour Changed";
updatetiles = true;
}
void editorclass::clamp_tilecol(const int rx, const int ry, const bool wrap /*= false*/)
{
const size_t roomnum = rx + ry*maxwidth;
if (!INBOUNDS_ARR(roomnum, level))
{
return;
}
edlevelclass& room = level[rx + ry*maxwidth];
const int tileset = room.tileset;
int tilecol = room.tilecol;
int mincol = -1;
int maxcol = 5;
// Only Space Station allows tileset -1
if (tileset != 0)
{
mincol = 0;
}
switch (tileset)
{
case 0:
maxcol = 31;
break;
case 1:
maxcol = 7;
break;
case 3:
maxcol = 6;
break;
case 5:
maxcol = 29;
break;
}
// If wrap is true, wrap-around, otherwise just cap
if (tilecol > maxcol)
{
tilecol = (wrap ? mincol : maxcol);
}
if (tilecol < mincol)
{
tilecol = (wrap ? maxcol : mincol);
}
room.tilecol = tilecol;
}
void editorclass::switch_enemy(const bool reversed /*= false*/)
{
const size_t roomnum = levx + levy*maxwidth;
if (!INBOUNDS_ARR(roomnum, level))
{
return;
}
edlevelclass& room = level[roomnum];
int enemy = room.enemytype;
if (reversed)
{
enemy--;
}
else
{
enemy++;
}
const int modulus = 10;
enemy = (enemy % modulus + modulus) % modulus;
room.enemytype = enemy;
note = "Enemy Type Changed";
notedelay = 45;
}
bool editorclass::load(std::string& _path)
2020-01-01 21:29:24 +01:00
{
reset();
static const char *levelDir = "levels/";
if (_path.compare(0, strlen(levelDir), levelDir) != 0)
{
_path = levelDir + _path;
}
FILESYSTEM_unmountassets();
if (game.playassets != "")
{
FILESYSTEM_mountassets(game.playassets.c_str());
}
else
{
FILESYSTEM_mountassets(_path.c_str());
}
tinyxml2::XMLDocument doc;
if (!FILESYSTEM_loadTiXml2Document(_path.c_str(), doc))
2020-01-01 21:29:24 +01:00
{
printf("No level %s to load :(\n", _path.c_str());
return false;
2020-01-01 21:29:24 +01:00
}
loaded_filepath = _path;
2020-01-01 21:29:24 +01:00
tinyxml2::XMLHandle hDoc(&doc);
tinyxml2::XMLElement* pElem;
tinyxml2::XMLHandle hRoot(NULL);
2020-01-01 21:29:24 +01:00
version = 0;
{
pElem=hDoc.FirstChildElement().ToElement();
2020-01-01 21:29:24 +01:00
// should always have a valid root but handle gracefully if it does
if (!pElem)
{
printf("No valid root! Corrupt level file?\n");
}
pElem->QueryIntAttribute("version", &version);
// save this for later
hRoot=tinyxml2::XMLHandle(pElem);
2020-01-01 21:29:24 +01:00
}
for( pElem = hRoot.FirstChildElement( "Data" ).FirstChild().ToElement(); pElem; pElem=pElem->NextSiblingElement())
2020-01-01 21:29:24 +01:00
{
std::string pKey(pElem->Value());
const char* pText = pElem->GetText() ;
if(pText == NULL)
{
pText = "";
}
if (pKey == "MetaData")
{
for( tinyxml2::XMLElement* subElem = pElem->FirstChildElement(); subElem; subElem= subElem->NextSiblingElement())
2020-01-01 21:29:24 +01:00
{
std::string pKey_(subElem->Value());
const char* pText_ = subElem->GetText() ;
if(pText_ == NULL)
2020-01-01 21:29:24 +01:00
{
pText_ = "";
2020-01-01 21:29:24 +01:00
}
if(pKey_ == "Creator")
2020-01-01 21:29:24 +01:00
{
EditorData::GetInstance().creator = pText_;
2020-01-01 21:29:24 +01:00
}
if(pKey_ == "Title")
2020-01-01 21:29:24 +01:00
{
EditorData::GetInstance().title = pText_;
2020-01-01 21:29:24 +01:00
}
if(pKey_ == "Desc1")
2020-01-01 21:29:24 +01:00
{
Desc1 = pText_;
2020-01-01 21:29:24 +01:00
}
if(pKey_ == "Desc2")
2020-01-01 21:29:24 +01:00
{
Desc2 = pText_;
2020-01-01 21:29:24 +01:00
}
if(pKey_ == "Desc3")
2020-01-01 21:29:24 +01:00
{
Desc3 = pText_;
2020-01-01 21:29:24 +01:00
}
if(pKey_ == "website")
2020-01-01 21:29:24 +01:00
{
website = pText_;
2020-01-01 21:29:24 +01:00
}
if(pKey_ == "onewaycol_override")
{
onewaycol_override = help.Int(pText_);
}
2020-01-01 21:29:24 +01:00
}
}
if (pKey == "mapwidth")
{
mapwidth = help.Int(pText);
2020-01-01 21:29:24 +01:00
}
if (pKey == "mapheight")
{
mapheight = help.Int(pText);
2020-01-01 21:29:24 +01:00
}
if (pKey == "levmusic")
{
levmusic = help.Int(pText);
2020-01-01 21:29:24 +01:00
}
if (pKey == "contents")
{
std::string TextString = (pText);
if(TextString.length())
{
std::vector<std::string> values = split(TextString,',');
SDL_memset(contents, 0, sizeof(contents));
2020-01-01 21:29:24 +01:00
int x =0;
int y =0;
for(size_t i = 0; i < values.size(); i++)
{
contents[x + (maxwidth*40*y)] = help.Int(values[i].c_str());
2020-01-01 21:29:24 +01:00
x++;
if(x == mapwidth*40)
{
x=0;
y++;
}
}
}
}
/*else if(version==1){
if (pKey == "contents")
{
std::string TextString = (pText);
if(TextString.length())
{
std::vector<std::string> values = split(TextString,',');
contents.clear();
for(int i = 0; i < values.size(); i++)
{
contents.push_back(help.Int(values[i].c_str()));
2020-01-01 21:29:24 +01:00
}
}
}
//}
*/
if (pKey == "edEntities")
{
for( tinyxml2::XMLElement* edEntityEl = pElem->FirstChildElement(); edEntityEl; edEntityEl=edEntityEl->NextSiblingElement())
2020-01-01 21:29:24 +01:00
{
edentities entity;
2020-01-01 21:29:24 +01:00
std::string pKey(edEntityEl->Value());
if (edEntityEl->GetText() != NULL)
2020-01-01 21:29:24 +01:00
{
std::string text(edEntityEl->GetText());
// And now we come to the part where we have to deal with
// the terrible decisions of the past.
//
// For some reason, the closing tag of edentities generated
// by 2.2 and below has not only been put on a separate
// line, but also indented to match with the opening tag as
// well. Like this:
//
// <edentity ...>contents
// </edentity>
//
// Instead of doing <edentity ...>contents</edentity>.
//
// This is COMPLETELY terrible. This requires the XML to be
// parsed in an extremely specific and quirky way, which
// TinyXML-1 just happened to do.
//
// TinyXML-2 by default interprets the newline and the next
// indentation of whitespace literally, so you end up with
// tag contents that has a linefeed plus a bunch of extra
// spaces. You can't fix this by setting the whitespace
// mode to COLLAPSE_WHITESPACE, that does way more than
// TinyXML-1 ever did - it removes the leading whitespace
// from things like <edentity ...> this</edentity>, and
// collapses XML-encoded whitespace like <edentity ...>
// &#32; &#32;this</edentity>, which TinyXML-1 never did.
//
// Best solution here is to specifically hardcode removing
// the linefeed + the extremely specific amount of
// whitespace at the end of the contents.
if (endsWith(text, "\n ")) // linefeed + exactly 12 spaces
{
// 12 spaces + 1 linefeed = 13 chars
text = text.substr(0, text.length()-13);
}
entity.scriptname = text;
2020-01-01 21:29:24 +01:00
}
edEntityEl->QueryIntAttribute("x", &entity.x);
edEntityEl->QueryIntAttribute("y", &entity.y);
edEntityEl->QueryIntAttribute("t", &entity.t);
edEntityEl->QueryIntAttribute("p1", &entity.p1);
edEntityEl->QueryIntAttribute("p2", &entity.p2);
edEntityEl->QueryIntAttribute("p3", &entity.p3);
edEntityEl->QueryIntAttribute("p4", &entity.p4);
edEntityEl->QueryIntAttribute("p5", &entity.p5);
edEntityEl->QueryIntAttribute("p6", &entity.p6);
edentity.push_back(entity);
2020-01-01 21:29:24 +01:00
}
}
if (pKey == "levelMetaData")
{
int i = 0;
for( tinyxml2::XMLElement* edLevelClassElement = pElem->FirstChildElement(); edLevelClassElement; edLevelClassElement=edLevelClassElement->NextSiblingElement())
2020-01-01 21:29:24 +01:00
{
std::string pKey(edLevelClassElement->Value());
if(edLevelClassElement->GetText() != NULL)
{
level[i].roomname = std::string(edLevelClassElement->GetText()) ;
}
edLevelClassElement->QueryIntAttribute("tileset", &level[i].tileset);
edLevelClassElement->QueryIntAttribute("tilecol", &level[i].tilecol);
edLevelClassElement->QueryIntAttribute("platx1", &level[i].platx1);
edLevelClassElement->QueryIntAttribute("platy1", &level[i].platy1);
edLevelClassElement->QueryIntAttribute("platx2", &level[i].platx2);
edLevelClassElement->QueryIntAttribute("platy2", &level[i].platy2);
edLevelClassElement->QueryIntAttribute("platv", &level[i].platv);
edLevelClassElement->QueryIntAttribute("enemyx1", &level[i].enemyx1);
edLevelClassElement->QueryIntAttribute("enemyy1", &level[i].enemyy1);
edLevelClassElement->QueryIntAttribute("enemyx2", &level[i].enemyx2);
edLevelClassElement->QueryIntAttribute("enemyy2", &level[i].enemyy2);
edLevelClassElement->QueryIntAttribute("enemytype", &level[i].enemytype);
edLevelClassElement->QueryIntAttribute("directmode", &level[i].directmode);
edLevelClassElement->QueryIntAttribute("warpdir", &level[i].warpdir);
i++;
}
}
if (pKey == "script")
{
std::string TextString = (pText);
if(TextString.length())
{
std::vector<std::string> values = split(TextString,'|');
script.clearcustom();
Script script_;
bool headerfound = false;
2020-01-01 21:29:24 +01:00
for(size_t i = 0; i < values.size(); i++)
{
std::string& line = values[i];
//Comparing line[line.length()-1] directly to a string literal is UB
//Workaround: assign line[line.length()-1] to a string first
std::string temp;
if(line.length())
{
temp = line[line.length()-1];
}
if(temp == ":")
{
if(headerfound)
{
//Add the script if we have a preceding header
script.customscripts.push_back(script_);
}
script_.name = line.substr(0, line.length()-1);
script_.contents.clear();
headerfound = true;
continue;
}
if(headerfound)
{
script_.contents.push_back(line);
}
}
//Add the last script
if(headerfound)
{
//Add the script if we have a preceding header
script.customscripts.push_back(script_);
2020-01-01 21:29:24 +01:00
}
}
}
}
gethooks();
version=2;
return true;
2020-01-01 21:29:24 +01:00
}
bool editorclass::save(std::string& _path)
2020-01-01 21:29:24 +01:00
{
tinyxml2::XMLDocument doc;
std::string newpath("levels/" + _path);
// Try to preserve the XML of the currently-loaded one
bool already_exists = !loaded_filepath.empty() && FILESYSTEM_loadTiXml2Document(loaded_filepath.c_str(), doc);
if (!already_exists && !loaded_filepath.empty())
{
printf("Currently-loaded %s not found\n", loaded_filepath.c_str());
}
loaded_filepath = newpath;
tinyxml2::XMLElement* msg;
2020-01-01 21:29:24 +01:00
xml::update_declaration(doc);
tinyxml2::XMLElement * root = xml::update_element(doc, "MapData");
2020-01-01 21:29:24 +01:00
root->SetAttribute("version",version);
xml::update_comment(root, " Save file ");
2020-01-01 21:29:24 +01:00
tinyxml2::XMLElement * data = xml::update_element(root, "Data");
2020-01-01 21:29:24 +01:00
msg = xml::update_element(data, "MetaData");
2020-01-01 21:29:24 +01:00
//getUser
xml::update_tag(msg, "Creator", EditorData::GetInstance().creator.c_str());
2020-01-01 21:29:24 +01:00
xml::update_tag(msg, "Title", EditorData::GetInstance().title.c_str());
2020-01-01 21:29:24 +01:00
xml::update_tag(msg, "Created", version);
2020-01-01 21:29:24 +01:00
xml::update_tag(msg, "Modified", EditorData::GetInstance().modifier.c_str());
2020-01-01 21:29:24 +01:00
xml::update_tag(msg, "Modifiers", version);
2020-01-01 21:29:24 +01:00
xml::update_tag(msg, "Desc1", Desc1.c_str());
2020-01-01 21:29:24 +01:00
xml::update_tag(msg, "Desc2", Desc2.c_str());
2020-01-01 21:29:24 +01:00
xml::update_tag(msg, "Desc3", Desc3.c_str());
2020-01-01 21:29:24 +01:00
xml::update_tag(msg, "website", website.c_str());
2020-01-01 21:29:24 +01:00
if (onewaycol_override)
{
xml::update_tag(msg, "onewaycol_override", onewaycol_override);
}
else
{
// Delete the element. I could just delete one, but just to be sure,
// I will delete all of them if there are more than one
tinyxml2::XMLElement* element;
while ((element = msg->FirstChildElement("onewaycol_override"))
!= NULL)
{
doc.DeleteNode(element);
}
}
xml::update_tag(data, "mapwidth", mapwidth);
2020-01-01 21:29:24 +01:00
xml::update_tag(data, "mapheight", mapheight);
2020-01-01 21:29:24 +01:00
xml::update_tag(data, "levmusic", levmusic);
2020-01-01 21:29:24 +01:00
//New save format
std::string contentsString="";
for(int y = 0; y < mapheight*30; y++ )
{
for(int x = 0; x < mapwidth*40; x++ )
{
contentsString += help.String(contents[x + (maxwidth*40*y)]) + ",";
2020-01-01 21:29:24 +01:00
}
}
xml::update_tag(data, "contents", contentsString.c_str());
2020-01-01 21:29:24 +01:00
msg = xml::update_element_delete_contents(data, "edEntities");
for(size_t i = 0; i < edentity.size(); i++)
2020-01-01 21:29:24 +01:00
{
tinyxml2::XMLElement *edentityElement = doc.NewElement( "edentity" );
2020-01-01 21:29:24 +01:00
edentityElement->SetAttribute( "x", edentity[i].x);
edentityElement->SetAttribute( "y", edentity[i].y);
edentityElement->SetAttribute( "t", edentity[i].t);
edentityElement->SetAttribute( "p1", edentity[i].p1);
edentityElement->SetAttribute( "p2", edentity[i].p2);
edentityElement->SetAttribute( "p3", edentity[i].p3);
edentityElement->SetAttribute( "p4", edentity[i].p4);
edentityElement->SetAttribute( "p5", edentity[i].p5);
edentityElement->SetAttribute( "p6", edentity[i].p6);
edentityElement->LinkEndChild( doc.NewText( edentity[i].scriptname.c_str() )) ;
2020-01-01 21:29:24 +01:00
msg->LinkEndChild( edentityElement );
}
msg = xml::update_element_delete_contents(data, "levelMetaData");
for(size_t i = 0; i < SDL_arraysize(level); i++)
2020-01-01 21:29:24 +01:00
{
tinyxml2::XMLElement *edlevelclassElement = doc.NewElement( "edLevelClass" );
2020-01-01 21:29:24 +01:00
edlevelclassElement->SetAttribute( "tileset", level[i].tileset);
edlevelclassElement->SetAttribute( "tilecol", level[i].tilecol);
edlevelclassElement->SetAttribute( "platx1", level[i].platx1);
edlevelclassElement->SetAttribute( "platy1", level[i].platy1);
edlevelclassElement->SetAttribute( "platx2", level[i].platx2);
edlevelclassElement->SetAttribute( "platy2", level[i].platy2);
edlevelclassElement->SetAttribute( "platv", level[i].platv);
edlevelclassElement->SetAttribute( "enemyx1", level[i].enemyx1);
edlevelclassElement->SetAttribute( "enemyy1", level[i].enemyy1);
edlevelclassElement->SetAttribute( "enemyx2", level[i].enemyx2);
edlevelclassElement->SetAttribute( "enemyy2", level[i].enemyy2);
edlevelclassElement->SetAttribute( "enemytype", level[i].enemytype);
edlevelclassElement->SetAttribute( "directmode", level[i].directmode);
edlevelclassElement->SetAttribute( "warpdir", level[i].warpdir);
edlevelclassElement->LinkEndChild( doc.NewText( level[i].roomname.c_str() )) ;
2020-01-01 21:29:24 +01:00
msg->LinkEndChild( edlevelclassElement );
}
std::string scriptString;
for(size_t i = 0; i < script.customscripts.size(); i++)
2020-01-01 21:29:24 +01:00
{
Script& script_ = script.customscripts[i];
scriptString += script_.name + ":|";
for (size_t ii = 0; ii < script_.contents.size(); i++)
{
scriptString += script_.contents[ii];
// Inserts a space if the line ends with a :
if (script_.contents[ii].length() && *script_.contents[ii].rbegin() == ':')
{
scriptString += " ";
}
scriptString += "|";
}
2020-01-01 21:29:24 +01:00
}
xml::update_tag(data, "script", scriptString.c_str());
2020-01-01 21:29:24 +01:00
return FILESYSTEM_saveTiXml2Document(newpath.c_str(), doc);
2020-01-01 21:29:24 +01:00
}
static void addedentity( int xp, int yp, int tp, int p1 = 0, int p2 = 0, int p3 = 0, int p4 = 0, int p5 = 320, int p6 = 240)
2020-01-01 21:29:24 +01:00
{
edentities entity;
entity.x=xp;
entity.y=yp;
entity.t=tp;
entity.p1=p1;
entity.p2=p2;
entity.p3=p3;
entity.p4=p4;
entity.p5=p5;
entity.p6=p6;
entity.scriptname="";
edentity.push_back(entity);
2020-01-01 21:29:24 +01:00
}
static void removeedentity( int t )
2020-01-01 21:29:24 +01:00
{
edentity.erase(edentity.begin() + t);
2020-01-01 21:29:24 +01:00
}
static int edentat( int xp, int yp )
2020-01-01 21:29:24 +01:00
{
for(size_t i=0; i<edentity.size(); i++)
2020-01-01 21:29:24 +01:00
{
if(edentity[i].x==xp && edentity[i].y==yp) return i;
}
return -1;
}
static void fillbox( int x, int y, int x2, int y2, int c )
2020-01-01 21:29:24 +01:00
{
FillRect(graphics.backBuffer, x, y, x2-x, 1, c);
FillRect(graphics.backBuffer, x, y2-1, x2-x, 1, c);
FillRect(graphics.backBuffer, x, y, 1, y2-y, c);
FillRect(graphics.backBuffer, x2-1, y, 1, y2-y, c);
2020-01-01 21:29:24 +01:00
}
static void fillboxabs( int x, int y, int x2, int y2, int c )
2020-01-01 21:29:24 +01:00
{
FillRect(graphics.backBuffer, x, y, x2, 1, c);
FillRect(graphics.backBuffer, x, y+y2-1, x2, 1, c);
FillRect(graphics.backBuffer, x, y, 1, y2, c);
FillRect(graphics.backBuffer, x+x2-1, y, 1, y2, c);
2020-01-01 21:29:24 +01:00
}
void editorclass::generatecustomminimap()
2020-01-01 21:29:24 +01:00
{
map.customwidth=mapwidth;
map.customheight=mapheight;
map.customzoom=1;
if(map.customwidth<=10 && map.customheight<=10) map.customzoom=2;
if(map.customwidth<=5 && map.customheight<=5) map.customzoom=4;
//Set minimap offsets
if(map.customzoom==4)
{
map.custommmxoff=24*(5-map.customwidth);
map.custommmxsize=240-(map.custommmxoff*2);
map.custommmyoff=18*(5-map.customheight);
map.custommmysize=180-(map.custommmyoff*2);
}
else if(map.customzoom==2)
{
map.custommmxoff=12*(10-map.customwidth);
map.custommmxsize=240-(map.custommmxoff*2);
map.custommmyoff=9*(10-map.customheight);
map.custommmysize=180-(map.custommmyoff*2);
}
else
{
map.custommmxoff=6*(20-map.customwidth);
map.custommmxsize=240-(map.custommmxoff*2);
map.custommmyoff=int(4.5*(20-map.customheight));
map.custommmysize=180-(map.custommmyoff*2);
}
FillRect(graphics.images[12], graphics.getRGB(0,0,0));
2020-01-01 21:29:24 +01:00
int tm=0;
int temp=0;
//Scan over the map size
if(mapheight<=5 && mapwidth<=5)
2020-01-01 21:29:24 +01:00
{
//4x map
for(int j2=0; j2<mapheight; j2++)
2020-01-01 21:29:24 +01:00
{
for(int i2=0; i2<mapwidth; i2++)
2020-01-01 21:29:24 +01:00
{
//Ok, now scan over each square
tm=196;
if(level[i2 + (j2*maxwidth)].tileset==1) tm=96;
2020-01-01 21:29:24 +01:00
for(int j=0; j<36; j++)
{
for(int i=0; i<48; i++)
{
temp=absfree(int(i*0.83) + (i2*40),int(j*0.83)+(j2*30));
2020-01-01 21:29:24 +01:00
if(temp>=1)
{
//Fill in this pixel
FillRect(graphics.images[12], (i2*48)+i, (j2*36)+j, 1, 1, graphics.getRGB(tm, tm, tm));
2020-01-01 21:29:24 +01:00
}
}
}
}
}
}
else if(mapheight<=10 && mapwidth<=10)
2020-01-01 21:29:24 +01:00
{
//2x map
for(int j2=0; j2<mapheight; j2++)
2020-01-01 21:29:24 +01:00
{
for(int i2=0; i2<mapwidth; i2++)
2020-01-01 21:29:24 +01:00
{
//Ok, now scan over each square
tm=196;
if(level[i2 + (j2*maxwidth)].tileset==1) tm=96;
2020-01-01 21:29:24 +01:00
for(int j=0; j<18; j++)
{
for(int i=0; i<24; i++)
{
temp=absfree(int(i*1.6) + (i2*40),int(j*1.6)+(j2*30));
2020-01-01 21:29:24 +01:00
if(temp>=1)
{
//Fill in this pixel
FillRect(graphics.images[12], (i2*24)+i, (j2*18)+j, 1, 1, graphics.getRGB(tm, tm, tm));
2020-01-01 21:29:24 +01:00
}
}
}
}
}
}
else
{
for(int j2=0; j2<mapheight; j2++)
2020-01-01 21:29:24 +01:00
{
for(int i2=0; i2<mapwidth; i2++)
2020-01-01 21:29:24 +01:00
{
//Ok, now scan over each square
tm=196;
if(level[i2 + (j2*maxwidth)].tileset==1) tm=96;
2020-01-01 21:29:24 +01:00
for(int j=0; j<9; j++)
{
for(int i=0; i<12; i++)
{
temp=absfree(3+(i*3) + (i2*40),(j*3)+(j2*30));
2020-01-01 21:29:24 +01:00
if(temp>=1)
{
//Fill in this pixel
FillRect(graphics.images[12], (i2*12)+i, (j2*9)+j, 1, 1, graphics.getRGB(tm, tm, tm));
2020-01-01 21:29:24 +01:00
}
}
}
}
}
}
}
#if !defined(NO_EDITOR)
static void editormenurender(int tr, int tg, int tb)
{
extern editorclass ed;
switch (game.currentmenuname)
{
case Menu::ed_settings:
graphics.bigprint( -1, 75, "Map Settings", tr, tg, tb, true);
if (game.currentmenuoption == 3)
{
if (!game.ghostsenabled)
graphics.Print(2, 230, "Editor ghost trail is OFF", tr/2, tg/2, tb/2);
else
graphics.Print(2, 230, "Editor ghost trail is ON", tr, tg, tb);
}
break;
case Menu::ed_desc:
if(ed.titlemod)
{
if(ed.entframe<2)
{
graphics.bigprint( -1, 35, key.keybuffer+"_", tr, tg, tb, true);
}
else
{
graphics.bigprint( -1, 35, key.keybuffer+" ", tr, tg, tb, true);
}
}
else
{
graphics.bigprint( -1, 35, EditorData::GetInstance().title, tr, tg, tb, true);
}
if(ed.creatormod)
{
if(ed.entframe<2)
{
graphics.Print( -1, 60, "by " + key.keybuffer+ "_", tr, tg, tb, true);
}
else
{
graphics.Print( -1, 60, "by " + key.keybuffer+ " ", tr, tg, tb, true);
}
}
else
{
graphics.Print( -1, 60, "by " + EditorData::GetInstance().creator, tr, tg, tb, true);
}
if(ed.websitemod)
{
if(ed.entframe<2)
{
graphics.Print( -1, 70, key.keybuffer+"_", tr, tg, tb, true);
}
else
{
graphics.Print( -1, 70, key.keybuffer+" ", tr, tg, tb, true);
}
}
else
{
graphics.Print( -1, 70, ed.website, tr, tg, tb, true);
}
if(ed.desc1mod)
{
if(ed.entframe<2)
{
graphics.Print( -1, 90, key.keybuffer+"_", tr, tg, tb, true);
}
else
{
graphics.Print( -1, 90, key.keybuffer+" ", tr, tg, tb, true);
}
}
else
{
graphics.Print( -1, 90, ed.Desc1, tr, tg, tb, true);
}
if(ed.desc2mod)
{
if(ed.entframe<2)
{
graphics.Print( -1, 100, key.keybuffer+"_", tr, tg, tb, true);
}
else
{
graphics.Print( -1, 100, key.keybuffer+" ", tr, tg, tb, true);
}
}
else
{
graphics.Print( -1, 100, ed.Desc2, tr, tg, tb, true);
}
if(ed.desc3mod)
{
if(ed.entframe<2)
{
graphics.Print( -1, 110, key.keybuffer+"_", tr, tg, tb, true);
}
else
{
graphics.Print( -1, 110, key.keybuffer+" ", tr, tg, tb, true);
}
}
else
{
graphics.Print( -1, 110, ed.Desc3, tr, tg, tb, true);
}
break;
case Menu::ed_music:
{
graphics.bigprint( -1, 65, "Map Music", tr, tg, tb, true);
graphics.Print( -1, 85, "Current map music:", tr, tg, tb, true);
std::string songname;
switch(ed.levmusic)
{
case 0:
songname = "No background music";
break;
case 1:
songname = "1: Pushing Onwards";
break;
case 2:
songname = "2: Positive Force";
break;
case 3:
songname = "3: Potential for Anything";
break;
case 4:
songname = "4: Passion for Exploring";
break;
case 5:
songname = "N/A: Pause";
break;
case 6:
songname = "5: Presenting VVVVVV";
break;
case 7:
songname = "N/A: Plenary";
break;
case 8:
songname = "6: Predestined Fate";
break;
case 9:
songname = "N/A: ecroF evitisoP";
break;
case 10:
songname = "7: Popular Potpourri";
break;
case 11:
songname = "8: Pipe Dream";
break;
case 12:
songname = "9: Pressure Cooker";
break;
case 13:
songname = "10: Paced Energy";
break;
case 14:
songname = "11: Piercing the Sky";
break;
case 15:
songname = "N/A: Predestined Fate Remix";
break;
default:
songname = "?: something else";
break;
}
graphics.Print( -1, 120, songname, tr, tg, tb, true);
break;
}
case Menu::ed_quit:
graphics.bigprint( -1, 90, "Save before", tr, tg, tb, true);
graphics.bigprint( -1, 110, "quitting?", tr, tg, tb, true);
break;
default:
break;
}
}
void editorrender()
2020-01-01 21:29:24 +01:00
{
extern editorclass ed;
if (game.shouldreturntoeditor)
{
graphics.backgrounddrawn = false;
}
2020-01-01 21:29:24 +01:00
//Draw grid
FillRect(graphics.backBuffer, 0, 0, 320,240, graphics.getRGB(0,0,0));
2020-01-01 21:29:24 +01:00
for(int j=0; j<30; j++)
{
for(int i=0; i<40; i++)
{
fillbox(i*8, j*8, (i*8)+7, (j*8)+7, graphics.getRGB(8,8,8)); //a simple grid
if(i%4==0) fillbox(i*8, j*8, (i*8)+7, (j*8)+7, graphics.getRGB(16,16,16));
if(j%4==0) fillbox(i*8, j*8, (i*8)+7, (j*8)+7, graphics.getRGB(16,16,16));
2020-01-01 21:29:24 +01:00
//Minor guides
if(i==9) fillbox(i*8, j*8, (i*8)+7, (j*8)+7, graphics.getRGB(24,24,24));
if(i==30) fillbox(i*8, j*8, (i*8)+7, (j*8)+7, graphics.getRGB(24,24,24));
if(j==6 || j==7) fillbox(i*8, j*8, (i*8)+7, (j*8)+7, graphics.getRGB(24,24,24));
if(j==21 || j==22) fillbox(i*8, j*8, (i*8)+7, (j*8)+7, graphics.getRGB(24,24,24));
2020-01-01 21:29:24 +01:00
//Major guides
if(i==20 || i==19) fillbox(i*8, j*8, (i*8)+7, (j*8)+7, graphics.getRGB(32,32,32));
if(j==14) fillbox(i*8, j*8, (i*8)+7, (j*8)+7, graphics.getRGB(32,32,32));
2020-01-01 21:29:24 +01:00
}
}
//Or draw background
if(!ed.settingsmod)
{
switch(ed.level[ed.levx+(ed.levy*ed.maxwidth)].warpdir)
{
case 1:
graphics.rcol=ed.getwarpbackground(ed.levx, ed.levy);
graphics.drawbackground(3);
2020-01-01 21:29:24 +01:00
break;
case 2:
graphics.rcol=ed.getwarpbackground(ed.levx, ed.levy);
graphics.drawbackground(4);
2020-01-01 21:29:24 +01:00
break;
case 3:
graphics.rcol=ed.getwarpbackground(ed.levx, ed.levy);
graphics.drawbackground(5);
2020-01-01 21:29:24 +01:00
break;
default:
break;
}
}
//Draw map, in function
int temp;
if(ed.level[ed.levx+(ed.maxwidth*ed.levy)].tileset==0 || ed.level[ed.levx+(ed.maxwidth*ed.levy)].tileset==10)
{
for (int j = 0; j < 30; j++)
{
for (int i = 0; i < 40; i++)
{
temp=ed.contents[i + (ed.levx*40) + ed.vmult[j+(ed.levy*30)]];
if(temp>0) graphics.drawtile(i*8,j*8,temp);
2020-01-01 21:29:24 +01:00
}
}
}
else
{
for (int j = 0; j < 30; j++)
{
for (int i = 0; i < 40; i++)
{
temp=ed.contents[i + (ed.levx*40) + ed.vmult[j+(ed.levy*30)]];
if(temp>0) graphics.drawtile2(i*8,j*8,temp);
2020-01-01 21:29:24 +01:00
}
}
}
//Edge tile fix
//Buffer the sides of the new room with tiles from other rooms, to ensure no gap problems.
for(int j=0; j<30; j++)
{
//left edge
if(ed.freewrap((ed.levx*40)-1,j+(ed.levy*30))==1)
{
FillRect(graphics.backBuffer, 0,j*8, 2,8, graphics.getRGB(255,255,255-help.glow));
2020-01-01 21:29:24 +01:00
}
//right edge
if(ed.freewrap((ed.levx*40)+40,j+(ed.levy*30))==1)
{
FillRect(graphics.backBuffer, 318,j*8, 2,8, graphics.getRGB(255,255,255-help.glow));
2020-01-01 21:29:24 +01:00
}
}
for(int i=0; i<40; i++)
{
if(ed.freewrap((ed.levx*40)+i,(ed.levy*30)-1)==1)
{
FillRect(graphics.backBuffer, i*8,0, 8,2, graphics.getRGB(255,255,255-help.glow));
2020-01-01 21:29:24 +01:00
}
if(ed.freewrap((ed.levx*40)+i,30+(ed.levy*30))==1)
{
FillRect(graphics.backBuffer, i*8,238, 8,2, graphics.getRGB(255,255,255-help.glow));
2020-01-01 21:29:24 +01:00
}
}
//Draw entities
obj.customplatformtile=game.customcol*12;
int temp2=edentat(ed.tilex+ (ed.levx*40),ed.tiley+ (ed.levy*30));
// Special case for drawing gray entities
int current_room = (ed.levx+(ed.levy*ed.maxwidth));
bool custom_gray = INBOUNDS_ARR(current_room, ed.level)
&& ed.level[current_room].tileset == 3 && ed.level[current_room].tilecol == 6;
colourTransform gray_ct;
gray_ct.colour = 0xFFFFFFFF;
// Draw entities backward to remain accurate with ingame
for (int i = edentity.size() - 1; i >= 0; i--)
2020-01-01 21:29:24 +01:00
{
point tpoint;
SDL_Rect drawRect;
//if() on screen
if(edentity[i].x / 40 == ed.levx && edentity[i].y / 30 == ed.levy)
2020-01-01 21:29:24 +01:00
{
switch(edentity[i].t)
{
case 1: //Entities
if (custom_gray) {
graphics.setcol(18);
ed.entcolreal = graphics.ct.colour;
}
graphics.drawsprite((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),ed.getenemyframe(ed.level[ed.levx+(ed.levy*ed.maxwidth)].enemytype),ed.entcolreal);
if(edentity[i].p1==0) graphics.Print((edentity[i].x*8)- (ed.levx*40*8)+4,(edentity[i].y*8)- (ed.levy*30*8)+4, "V", 255, 255, 255 - help.glow, false);
if(edentity[i].p1==1) graphics.Print((edentity[i].x*8)- (ed.levx*40*8)+4,(edentity[i].y*8)- (ed.levy*30*8)+4, "^", 255, 255, 255 - help.glow, false);
if(edentity[i].p1==2) graphics.Print((edentity[i].x*8)- (ed.levx*40*8)+4,(edentity[i].y*8)- (ed.levy*30*8)+4, "<", 255, 255, 255 - help.glow, false);
if(edentity[i].p1==3) graphics.Print((edentity[i].x*8)- (ed.levx*40*8)+4,(edentity[i].y*8)- (ed.levy*30*8)+4, ">", 255, 255, 255 - help.glow, false);
fillboxabs((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),16,16,graphics.getBGR(255,164,255));
2020-01-01 21:29:24 +01:00
break;
case 2: //Threadmills & platforms
if (!INBOUNDS_VEC(obj.customplatformtile, graphics.entcolours))
{
continue;
}
2020-01-01 21:29:24 +01:00
tpoint.x = (edentity[i].x*8)- (ed.levx*40*8);
tpoint.y = (edentity[i].y*8)- (ed.levy*30*8);
drawRect = graphics.tiles_rect;
2020-01-01 21:29:24 +01:00
drawRect.x += tpoint.x;
drawRect.y += tpoint.y;
for (int j = 0; j < 4; j++) {
if (custom_gray) BlitSurfaceTinted(graphics.entcolours[obj.customplatformtile],NULL, graphics.backBuffer, &drawRect, gray_ct);
else BlitSurfaceStandard(graphics.entcolours[obj.customplatformtile],NULL, graphics.backBuffer, &drawRect);
drawRect.x += 8;
}
2020-01-01 21:29:24 +01:00
if(edentity[i].p1<=4)
{
if(edentity[i].p1==0) graphics.Print((edentity[i].x*8)- (ed.levx*40*8)+12,(edentity[i].y*8)- (ed.levy*30*8), "V", 255 - help.glow, 255 - help.glow, 255 - help.glow, false);
if(edentity[i].p1==1) graphics.Print((edentity[i].x*8)- (ed.levx*40*8)+12,(edentity[i].y*8)- (ed.levy*30*8), "^", 255 - help.glow, 255 - help.glow, 255 - help.glow, false);
if(edentity[i].p1==2) graphics.Print((edentity[i].x*8)- (ed.levx*40*8)+12,(edentity[i].y*8)- (ed.levy*30*8), "<", 255 - help.glow, 255 - help.glow, 255 - help.glow, false);
if(edentity[i].p1==3) graphics.Print((edentity[i].x*8)- (ed.levx*40*8)+12,(edentity[i].y*8)- (ed.levy*30*8), ">", 255 - help.glow, 255 - help.glow, 255 - help.glow, false);
fillboxabs((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),32,8,graphics.getBGR(255,255,255));
2020-01-01 21:29:24 +01:00
}
if(edentity[i].p1==5)
{
graphics.Print((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8), ">>>>", 255 - help.glow, 255 - help.glow, 255 - help.glow, false);
fillboxabs((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),32,8,graphics.getBGR(255,255,255));
2020-01-01 21:29:24 +01:00
}
else if(edentity[i].p1==6)
{
graphics.Print((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8), "<<<<", 255 - help.glow, 255 - help.glow, 255 - help.glow, false);
fillboxabs((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),32,8,graphics.getBGR(255,255,255));
2020-01-01 21:29:24 +01:00
}
if(edentity[i].p1>=7)
{
tpoint.x = (edentity[i].x*8)- (ed.levx*40*8)+32;
tpoint.y = (edentity[i].y*8)- (ed.levy*30*8);
drawRect = graphics.tiles_rect;
2020-01-01 21:29:24 +01:00
drawRect.x += tpoint.x;
drawRect.y += tpoint.y;
for (int j = 0; j < 4; j++) {
if (custom_gray) BlitSurfaceTinted(graphics.entcolours[obj.customplatformtile],NULL, graphics.backBuffer, &drawRect, gray_ct);
else BlitSurfaceStandard(graphics.entcolours[obj.customplatformtile],NULL, graphics.backBuffer, &drawRect);
drawRect.x += 8;
}
2020-01-01 21:29:24 +01:00
}
if(edentity[i].p1==7)
{
graphics.Print((edentity[i].x*8)- (ed.levx*40*8)+4,(edentity[i].y*8)- (ed.levy*30*8), "> > > > ", 255 - help.glow, 255 - help.glow, 255 - help.glow, false);
fillboxabs((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),64,8,graphics.getBGR(255,255,255));
2020-01-01 21:29:24 +01:00
}
else if(edentity[i].p1==8)
{
graphics.Print((edentity[i].x*8)- (ed.levx*40*8)+4,(edentity[i].y*8)- (ed.levy*30*8), "< < < < ", 255 - help.glow, 255 - help.glow, 255 - help.glow, false);
fillboxabs((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),64,8,graphics.getBGR(255,255,255));
2020-01-01 21:29:24 +01:00
}
break;
case 3: //Disappearing Platform
if (!INBOUNDS_VEC(obj.customplatformtile, graphics.entcolours))
{
continue;
}
2020-01-01 21:29:24 +01:00
tpoint.x = (edentity[i].x*8)- (ed.levx*40*8);
tpoint.y = (edentity[i].y*8)- (ed.levy*30*8);
drawRect = graphics.tiles_rect;
2020-01-01 21:29:24 +01:00
drawRect.x += tpoint.x;
drawRect.y += tpoint.y;
for (int j = 0; j < 4; j++) {
if (custom_gray) BlitSurfaceTinted(graphics.entcolours[obj.customplatformtile],NULL, graphics.backBuffer, &drawRect, gray_ct);
else BlitSurfaceStandard(graphics.entcolours[obj.customplatformtile],NULL, graphics.backBuffer, &drawRect);
drawRect.x += 8;
}
2020-01-01 21:29:24 +01:00
graphics.Print((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8), "////", 255 - help.glow, 255 - help.glow, 255 - help.glow, false);
fillboxabs((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),32,8,graphics.getBGR(255,255,255));
2020-01-01 21:29:24 +01:00
break;
case 9: //Shiny Trinket
graphics.drawsprite((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),22,196,196,196);
fillboxabs((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),16,16,graphics.getRGB(164,164,255));
2020-01-01 21:29:24 +01:00
break;
case 10: //Checkpoints
if(edentity[i].p1==0) //From roof
{
graphics.drawsprite((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),20,196,196,196);
2020-01-01 21:29:24 +01:00
}
else if(edentity[i].p1==1) //From floor
{
graphics.drawsprite((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),21,196,196,196);
2020-01-01 21:29:24 +01:00
}
fillboxabs((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),16,16,graphics.getRGB(164,164,255));
2020-01-01 21:29:24 +01:00
break;
case 11: //Gravity lines
if(edentity[i].p1==0) //Horizontal
{
int tx=edentity[i].x-(ed.levx*40);
int tx2=edentity[i].x-(ed.levx*40);
int ty=edentity[i].y-(ed.levy*30);
if (edentity[i].p4 != 1)
{
// Unlocked
while(ed.spikefree(tx,ty)==0) tx--;
while(ed.spikefree(tx2,ty)==0) tx2++;
tx++;
edentity[i].p2=tx;
edentity[i].p3=(tx2-tx)*8;
}
else
{
// Locked
tx = edentity[i].p2;
tx2 = tx + edentity[i].p3/8;
}
FillRect(graphics.backBuffer, (tx*8),(ty*8)+4, (tx2-tx)*8,1, graphics.getRGB(194,194,194));
fillboxabs((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),8,8,graphics.getRGB(164,255,164));
2020-01-01 21:29:24 +01:00
}
else //Vertical
{
int tx=edentity[i].x-(ed.levx*40);
int ty=edentity[i].y-(ed.levy*30);
int ty2=edentity[i].y-(ed.levy*30);
if (edentity[i].p4 != 1)
{
// Unlocked
while(ed.spikefree(tx,ty)==0) ty--;
while(ed.spikefree(tx,ty2)==0) ty2++;
ty++;
edentity[i].p2=ty;
edentity[i].p3=(ty2-ty)*8;
}
else
{
// Locked
ty = edentity[i].p2;
ty2 = ty + edentity[i].p3/8;
}
FillRect(graphics.backBuffer, (tx*8)+3,(ty*8), 1,(ty2-ty)*8, graphics.getRGB(194,194,194));
fillboxabs((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),8,8,graphics.getRGB(164,255,164));
2020-01-01 21:29:24 +01:00
}
break;
case 13://Warp tokens
graphics.drawsprite((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),18+(ed.entframe%2),196,196,196);
fillboxabs((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),16,16,graphics.getRGB(164,164,255));
if(temp2==i)
2020-01-01 21:29:24 +01:00
{
graphics.bprint((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8)-8,
2020-01-01 21:29:24 +01:00
"("+help.String(((edentity[i].p1-int(edentity[i].p1%40))/40)+1)+","+help.String(((edentity[i].p2-int(edentity[i].p2%30))/30)+1)+")",210,210,255);
}
else
{
graphics.bprint((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8)-8,help.String(ed.findwarptoken(i)),210,210,255);
2020-01-01 21:29:24 +01:00
}
break;
case 15: //Crewmates
graphics.drawsprite((edentity[i].x*8)- (ed.levx*40*8)-4,(edentity[i].y*8)- (ed.levy*30*8),144,graphics.crewcolourreal(edentity[i].p1));
fillboxabs((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),16,24,graphics.getRGB(164,164,164));
2020-01-01 21:29:24 +01:00
break;
case 16: //Start
if(edentity[i].p1==0) //Left
{
graphics.drawsprite((edentity[i].x*8)- (ed.levx*40*8)-4,(edentity[i].y*8)- (ed.levy*30*8),0,graphics.col_crewcyan);
2020-01-01 21:29:24 +01:00
}
else if(edentity[i].p1==1)
{
graphics.drawsprite((edentity[i].x*8)- (ed.levx*40*8)-4,(edentity[i].y*8)- (ed.levy*30*8),3,graphics.col_crewcyan);
2020-01-01 21:29:24 +01:00
}
fillboxabs((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),16,24,graphics.getRGB(164,255,255));
2020-01-01 21:29:24 +01:00
if(ed.entframe<2)
{
graphics.bprint((edentity[i].x*8)- (ed.levx*40*8)-12,(edentity[i].y*8)- (ed.levy*30*8)-8,"START",255,255,255);
2020-01-01 21:29:24 +01:00
}
else
{
graphics.bprint((edentity[i].x*8)- (ed.levx*40*8)-12,(edentity[i].y*8)- (ed.levy*30*8)-8,"START",196,196,196);
2020-01-01 21:29:24 +01:00
}
break;
case 17: //Roomtext
if(edentity[i].scriptname.length()<1)
{
fillboxabs((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),8,8,graphics.getRGB(96,96,96));
2020-01-01 21:29:24 +01:00
}
else
{
int length = utf8::unchecked::distance(edentity[i].scriptname.begin(), edentity[i].scriptname.end());
fillboxabs((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),length*8,8,graphics.getRGB(96,96,96));
2020-01-01 21:29:24 +01:00
}
graphics.Print((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8), edentity[i].scriptname, 196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 18: //Terminals
graphics.drawsprite((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8)+8,17,96,96,96);
fillboxabs((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),16,24,graphics.getRGB(164,164,164));
if(temp2==i)
2020-01-01 21:29:24 +01:00
{
graphics.bprint((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8)-8,edentity[i].scriptname,210,210,255);
2020-01-01 21:29:24 +01:00
}
break;
case 19: //Script Triggers
fillboxabs((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),edentity[i].p1*8,edentity[i].p2*8,graphics.getRGB(255,164,255));
fillboxabs((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),8,8,graphics.getRGB(255,255,255));
if(temp2==i)
2020-01-01 21:29:24 +01:00
{
graphics.bprint((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8)-8,edentity[i].scriptname,210,210,255);
2020-01-01 21:29:24 +01:00
}
break;
case 50: //Warp lines
if(edentity[i].p1>=2) //Horizontal
{
int tx=edentity[i].x-(ed.levx*40);
int tx2=edentity[i].x-(ed.levx*40);
int ty=edentity[i].y-(ed.levy*30);
if (edentity[i].p4 != 1)
{
// Unlocked
while(ed.free(tx,ty)==0) tx--;
while(ed.free(tx2,ty)==0) tx2++;
tx++;
edentity[i].p2=tx;
edentity[i].p3=(tx2-tx)*8;
}
else
{
// Locked
tx = edentity[i].p2;
tx2 = tx + edentity[i].p3/8;
}
fillboxabs((tx*8),(ty*8)+1, (tx2-tx)*8,6, graphics.getRGB(255,255,194));
fillboxabs((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),8,8,graphics.getRGB(255,255,164));
2020-01-01 21:29:24 +01:00
}
else //Vertical
{
int tx=edentity[i].x-(ed.levx*40);
int ty=edentity[i].y-(ed.levy*30);
int ty2=edentity[i].y-(ed.levy*30);
if (edentity[i].p4 != 1)
{
// Unlocked
while(ed.free(tx,ty)==0) ty--;
while(ed.free(tx,ty2)==0) ty2++;
ty++;
edentity[i].p2=ty;
edentity[i].p3=(ty2-ty)*8;
}
else
{
// Locked
ty = edentity[i].p2;
ty2 = ty + edentity[i].p3/8;
}
fillboxabs((tx*8)+1,(ty*8), 6,(ty2-ty)*8, graphics.getRGB(255,255,194));
fillboxabs((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),8,8,graphics.getRGB(255,255,164));
2020-01-01 21:29:24 +01:00
}
break;
}
}
//Need to also check warp point destinations
if(edentity[i].t==13 && ed.warpent!=i)
{
if (edentity[i].p1 / 40 == ed.levx && edentity[i].p2 / 30 == ed.levy)
2020-01-01 21:29:24 +01:00
{
graphics.drawsprite((edentity[i].p1*8)- (ed.levx*40*8),(edentity[i].p2*8)- (ed.levy*30*8),18+(ed.entframe%2),64,64,64);
fillboxabs((edentity[i].p1*8)- (ed.levx*40*8),(edentity[i].p2*8)- (ed.levy*30*8),16,16,graphics.getRGB(64,64,96));
2020-01-01 21:29:24 +01:00
if(ed.tilex+(ed.levx*40)==edentity[i].p1 && ed.tiley+(ed.levy*30)==edentity[i].p2)
{
graphics.bprint((edentity[i].p1*8)- (ed.levx*40*8),(edentity[i].p2*8)- (ed.levy*30*8)-8,
2020-01-01 21:29:24 +01:00
"("+help.String(((edentity[i].x-int(edentity[i].x%40))/40)+1)+","+help.String(((edentity[i].y-int(edentity[i].y%30))/30)+1)+")",190,190,225);
}
else
{
graphics.bprint((edentity[i].p1*8)- (ed.levx*40*8),(edentity[i].p2*8)- (ed.levy*30*8)-8,help.String(ed.findwarptoken(i)),190,190,225);
2020-01-01 21:29:24 +01:00
}
}
}
}
if(ed.boundarymod>0)
{
if(ed.boundarymod==1)
{
fillboxabs(ed.tilex*8, ed.tiley*8, 8,8,graphics.getRGB(255-(help.glow/2),191+(help.glow),210+(help.glow/2)));
fillboxabs((ed.tilex*8)+2, (ed.tiley*8)+2, 4,4,graphics.getRGB(128-(help.glow/4),100+(help.glow/2),105+(help.glow/4)));
2020-01-01 21:29:24 +01:00
}
else if(ed.boundarymod==2)
{
if((ed.tilex*8)+8<=ed.boundx1 || (ed.tiley*8)+8<=ed.boundy1)
{
fillboxabs(ed.boundx1, ed.boundy1, 8, 8,graphics.getRGB(255-(help.glow/2),191+(help.glow),210+(help.glow/2)));
fillboxabs(ed.boundx1+2, ed.boundy1+2, 4, 4,graphics.getRGB(128-(help.glow/4),100+(help.glow/2),105+(help.glow/4)));
2020-01-01 21:29:24 +01:00
}
else
{
fillboxabs(ed.boundx1, ed.boundy1, (ed.tilex*8)+8-ed.boundx1,(ed.tiley*8)+8-ed.boundy1,graphics.getRGB(255-(help.glow/2),191+(help.glow),210+(help.glow/2)));
fillboxabs(ed.boundx1+2, ed.boundy1+2, (ed.tilex*8)+8-ed.boundx1-4,(ed.tiley*8)+8-ed.boundy1-4,graphics.getRGB(128-(help.glow/4),100+(help.glow/2),105+(help.glow/4)));
2020-01-01 21:29:24 +01:00
}
}
}
else
{
//Draw boundaries
int tmp=ed.levx+(ed.levy*ed.maxwidth);
if(ed.level[tmp].enemyx1!=0 && ed.level[tmp].enemyy1!=0
&& ed.level[tmp].enemyx2!=320 && ed.level[tmp].enemyy2!=240)
{
fillboxabs( ed.level[tmp].enemyx1, ed.level[tmp].enemyy1,
2020-01-01 21:29:24 +01:00
ed.level[tmp].enemyx2-ed.level[tmp].enemyx1,
ed.level[tmp].enemyy2-ed.level[tmp].enemyy1,
graphics.getBGR(255-(help.glow/2),64,64));
2020-01-01 21:29:24 +01:00
}
if(ed.level[tmp].platx1!=0 && ed.level[tmp].platy1!=0
&& ed.level[tmp].platx2!=320 && ed.level[tmp].platy2!=240)
{
fillboxabs( ed.level[tmp].platx1, ed.level[tmp].platy1,
2020-01-01 21:29:24 +01:00
ed.level[tmp].platx2-ed.level[tmp].platx1,
ed.level[tmp].platy2-ed.level[tmp].platy1,
graphics.getBGR(64,64,255-(help.glow/2)));
2020-01-01 21:29:24 +01:00
}
}
//Draw ghosts (spooky!)
if (game.ghostsenabled) {
SDL_FillRect(graphics.ghostbuffer, NULL, SDL_MapRGBA(graphics.ghostbuffer->format, 0, 0, 0, 0));
for (int i = 0; i < (int)ed.ghosts.size(); i++) {
if (i <= ed.currentghosts) { // We don't want all of them to show up at once :)
if (ed.ghosts[i].rx != ed.levx || ed.ghosts[i].ry != ed.levy
|| !INBOUNDS_VEC(ed.ghosts[i].frame, graphics.sprites))
continue;
point tpoint;
tpoint.x = ed.ghosts[i].x;
tpoint.y = ed.ghosts[i].y;
graphics.setcolreal(ed.ghosts[i].realcol);
Uint32 alpha = graphics.ct.colour & graphics.backBuffer->format->Amask;
Uint32 therest = graphics.ct.colour & 0x00FFFFFF;
alpha = (3 * (alpha >> 24) / 4) << 24;
graphics.ct.colour = therest | alpha;
SDL_Rect drawRect = graphics.sprites_rect;
drawRect.x += tpoint.x;
drawRect.y += tpoint.y;
BlitSurfaceColoured(graphics.sprites[ed.ghosts[i].frame],NULL, graphics.ghostbuffer, &drawRect, graphics.ct);
}
}
SDL_BlitSurface(graphics.ghostbuffer, NULL, graphics.backBuffer, &graphics.bg_rect);
}
2020-01-01 21:29:24 +01:00
//Draw Cursor
switch(ed.drawmode)
{
case 0:
case 1:
case 2:
case 9:
case 10:
case 12: //Single point
fillboxabs((ed.tilex*8),(ed.tiley*8),8,8, graphics.getRGB(200,32,32));
2020-01-01 21:29:24 +01:00
break;
case 3:
case 4:
case 8:
case 13://2x2
fillboxabs((ed.tilex*8),(ed.tiley*8),16,16, graphics.getRGB(200,32,32));
2020-01-01 21:29:24 +01:00
break;
case 5:
case 6:
case 7://Platform
fillboxabs((ed.tilex*8),(ed.tiley*8),32,8, graphics.getRGB(200,32,32));
2020-01-01 21:29:24 +01:00
break;
case 14: //X if not on edge
if(ed.tilex==0 || ed.tilex==39 || ed.tiley==0 || ed.tiley==29)
{
fillboxabs((ed.tilex*8),(ed.tiley*8),8,8, graphics.getRGB(200,32,32));
2020-01-01 21:29:24 +01:00
}
else
{
graphics.Print((ed.tilex*8),(ed.tiley*8),"X",255,0,0);
2020-01-01 21:29:24 +01:00
}
break;
case 11:
case 15:
case 16: //2x3
fillboxabs((ed.tilex*8),(ed.tiley*8),16,24, graphics.getRGB(200,32,32));
2020-01-01 21:29:24 +01:00
break;
}
if(ed.drawmode<3)
{
if(ed.bmod && ed.drawmode<2)
2020-01-01 21:29:24 +01:00
{
fillboxabs((ed.tilex*8),0,8,240,graphics.getRGB(200,32,32));
2020-01-01 21:29:24 +01:00
}
else if(ed.hmod && ed.drawmode<2)
{
fillboxabs(0,(ed.tiley*8),320,8,graphics.getRGB(200,32,32));
}
else if(ed.vmod && ed.drawmode<2)
{
fillboxabs((ed.tilex*8)-32,(ed.tiley*8)-32,24+48,24+48, graphics.getRGB(200,32,32));
}
else if(ed.cmod && ed.drawmode<2)
{
fillboxabs((ed.tilex*8)-24,(ed.tiley*8)-24,24+32,24+32, graphics.getRGB(200,32,32));
}
else if(ed.xmod && ed.drawmode<2)
{
fillboxabs((ed.tilex*8)-16,(ed.tiley*8)-16,24+16,24+16, graphics.getRGB(200,32,32));
}
else if(ed.zmod && ed.drawmode<2)
{
fillboxabs((ed.tilex*8)-8,(ed.tiley*8)-8,24,24, graphics.getRGB(200,32,32));
}
2020-01-01 21:29:24 +01:00
}
//If in directmode, show current directmode tile
if(ed.level[ed.levx+(ed.levy*ed.maxwidth)].directmode==1)
{
//Tile box for direct mode
int t2=0;
if(ed.dmtileeditor>0)
{
ed.dmtileeditor--;
if(ed.dmtileeditor<=4)
{
t2=(4-ed.dmtileeditor)*12;
}
//Draw five lines of the editor
temp=ed.dmtile-(ed.dmtile%40);
temp-=80;
FillRect(graphics.backBuffer, 0,-t2,320,40, graphics.getRGB(0,0,0));
FillRect(graphics.backBuffer, 0,-t2+40,320,2, graphics.getRGB(255,255,255));
2020-01-01 21:29:24 +01:00
if(ed.level[ed.levx+(ed.levy*ed.maxwidth)].tileset==0)
{
for(int i=0; i<40; i++)
{
graphics.drawtile(i*8,0-t2,(temp+1200+i)%1200);
graphics.drawtile(i*8,8-t2,(temp+1200+40+i)%1200);
graphics.drawtile(i*8,16-t2,(temp+1200+80+i)%1200);
graphics.drawtile(i*8,24-t2,(temp+1200+120+i)%1200);
graphics.drawtile(i*8,32-t2,(temp+1200+160+i)%1200);
2020-01-01 21:29:24 +01:00
}
}
else
{
for(int i=0; i<40; i++)
{
graphics.drawtile2(i*8,0-t2,(temp+1200+i)%1200);
graphics.drawtile2(i*8,8-t2,(temp+1200+40+i)%1200);
graphics.drawtile2(i*8,16-t2,(temp+1200+80+i)%1200);
graphics.drawtile2(i*8,24-t2,(temp+1200+120+i)%1200);
graphics.drawtile2(i*8,32-t2,(temp+1200+160+i)%1200);
2020-01-01 21:29:24 +01:00
}
}
//Highlight our little block
fillboxabs(((ed.dmtile%40)*8)-2,16-t2-2,12,12,graphics.getRGB(196, 196, 255 - help.glow));
fillboxabs(((ed.dmtile%40)*8)-1,16-t2-1,10,10,graphics.getRGB(0,0,0));
2020-01-01 21:29:24 +01:00
}
if(ed.dmtileeditor>0 && t2<=30)
{
graphics.bprint(2, 45-t2, "Tile:", 196, 196, 255 - help.glow, false);
graphics.bprint(58, 45-t2, help.String(ed.dmtile), 196, 196, 255 - help.glow, false);
FillRect(graphics.backBuffer, 44,44-t2,10,10, graphics.getRGB(196, 196, 255 - help.glow));
FillRect(graphics.backBuffer, 45,45-t2,8,8, graphics.getRGB(0,0,0));
2020-01-01 21:29:24 +01:00
if(ed.level[ed.levx+(ed.levy*ed.maxwidth)].tileset==0)
{
graphics.drawtile(45,45-t2,ed.dmtile);
2020-01-01 21:29:24 +01:00
}
else
{
graphics.drawtile2(45,45-t2,ed.dmtile);
2020-01-01 21:29:24 +01:00
}
}
else
{
graphics.bprint(2, 12, "Tile:", 196, 196, 255 - help.glow, false);
graphics.bprint(58, 12, help.String(ed.dmtile), 196, 196, 255 - help.glow, false);
FillRect(graphics.backBuffer, 44,11,10,10, graphics.getRGB(196, 196, 255 - help.glow));
FillRect(graphics.backBuffer, 45,12,8,8, graphics.getRGB(0,0,0));
2020-01-01 21:29:24 +01:00
if(ed.level[ed.levx+(ed.levy*ed.maxwidth)].tileset==0)
{
graphics.drawtile(45,12,ed.dmtile);
2020-01-01 21:29:24 +01:00
}
else
{
graphics.drawtile2(45,12,ed.dmtile);
2020-01-01 21:29:24 +01:00
}
}
}
//Draw GUI
if(ed.boundarymod>0)
{
if(ed.boundarymod==1)
{
FillRect(graphics.backBuffer, 0,230,320,240, graphics.getRGB(32,32,32));
FillRect(graphics.backBuffer, 0,231,320,240, graphics.getRGB(0,0,0));
2020-01-01 21:29:24 +01:00
switch(ed.boundarytype)
{
case 0:
graphics.Print(4, 232, "SCRIPT BOX: Click on top left", 255,255,255, false);
2020-01-01 21:29:24 +01:00
break;
case 1:
graphics.Print(4, 232, "ENEMY BOUNDS: Click on top left", 255,255,255, false);
2020-01-01 21:29:24 +01:00
break;
case 2:
graphics.Print(4, 232, "PLATFORM BOUNDS: Click on top left", 255,255,255, false);
2020-01-01 21:29:24 +01:00
break;
case 3:
graphics.Print(4, 232, "COPY TILES: Click on top left", 255,255,255, false);
2020-01-01 21:29:24 +01:00
break;
default:
graphics.Print(4, 232, "Click on top left", 255,255,255, false);
2020-01-01 21:29:24 +01:00
break;
}
}
else if(ed.boundarymod==2)
{
FillRect(graphics.backBuffer, 0,230,320,240, graphics.getRGB(32,32,32));
FillRect(graphics.backBuffer, 0,231,320,240, graphics.getRGB(0,0,0));
2020-01-01 21:29:24 +01:00
switch(ed.boundarytype)
{
case 0:
graphics.Print(4, 232, "SCRIPT BOX: Click on bottom right", 255,255,255, false);
2020-01-01 21:29:24 +01:00
break;
case 1:
graphics.Print(4, 232, "ENEMY BOUNDS: Click on bottom right", 255,255,255, false);
2020-01-01 21:29:24 +01:00
break;
case 2:
graphics.Print(4, 232, "PLATFORM BOUNDS: Click on bottom right", 255,255,255, false);
2020-01-01 21:29:24 +01:00
break;
case 3:
graphics.Print(4, 232, "COPY TILES: Click on bottom right", 255,255,255, false);
2020-01-01 21:29:24 +01:00
break;
default:
graphics.Print(4, 232, "Click on bottom right", 255,255,255, false);
2020-01-01 21:29:24 +01:00
break;
}
}
}
else if(ed.scripteditmod)
{
//Elaborate C64 BASIC menu goes here!
FillRect(graphics.backBuffer, 0,0,320,240, graphics.getBGR(123, 111, 218));
FillRect(graphics.backBuffer, 14,16,292,208, graphics.getRGB(162,48,61));
2020-01-01 21:29:24 +01:00
switch(ed.scripthelppage)
{
case 0:
graphics.Print(16,28,"**** VVVVVV SCRIPT EDITOR ****", 123, 111, 218, true);
graphics.Print(16,44,"PRESS ESC TO RETURN TO MENU", 123, 111, 218, true);
2020-01-01 21:29:24 +01:00
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
if(!ed.hooklist.empty())
2020-01-01 21:29:24 +01:00
{
for(int i=0; i<9; i++)
{
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
if(ed.hookmenupage+i<(int)ed.hooklist.size())
2020-01-01 21:29:24 +01:00
{
if(ed.hookmenupage+i==ed.hookmenu)
{
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
std::string tstring="> " + ed.hooklist[(ed.hooklist.size()-1)-(ed.hookmenupage+i)] + " <";
for (size_t ii = 0; ii < tstring.length(); ii++)
{
tstring[ii] = SDL_toupper(tstring[ii]);
}
graphics.Print(16,68+(i*16),tstring,123, 111, 218, true);
2020-01-01 21:29:24 +01:00
}
else
{
graphics.Print(16,68+(i*16),ed.hooklist[(ed.hooklist.size()-1)-(ed.hookmenupage+i)],123, 111, 218, true);
2020-01-01 21:29:24 +01:00
}
}
}
}
else
{
graphics.Print(16,110,"NO SCRIPT IDS FOUND", 123, 111, 218, true);
graphics.Print(16,130,"CREATE A SCRIPT WITH EITHER", 123, 111, 218, true);
graphics.Print(16,140,"THE TERMINAL OR SCRIPT BOX TOOLS", 123, 111, 218, true);
2020-01-01 21:29:24 +01:00
}
break;
case 1:
//Current scriptname
FillRect(graphics.backBuffer, 14,226,292,12, graphics.getRGB(162,48,61));
graphics.Print(16,228,"CURRENT SCRIPT: " + ed.sbscript, 123, 111, 218, true);
2020-01-01 21:29:24 +01:00
//Draw text
for(int i=0; i<25; i++)
{
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
if(i+ed.pagey<(int)ed.sb.size())
2020-01-01 21:29:24 +01:00
{
graphics.Print(16,20+(i*8),ed.sb[i+ed.pagey], 123, 111, 218, false);
2020-01-01 21:29:24 +01:00
}
}
//Draw cursor
if(ed.entframe<2)
{
graphics.Print(16+(ed.sbx*8),20+(ed.sby*8),"_",123, 111, 218, false);
2020-01-01 21:29:24 +01:00
}
break;
}
}
else if(ed.settingsmod)
{
if(!game.colourblindmode)
{
graphics.drawtowerbackground(graphics.titlebg);
}
else
{
FillRect(graphics.backBuffer, 0, 0, 320, 240, 0x00000000);
}
2020-01-01 21:29:24 +01:00
int tr = graphics.titlebg.r - (help.glow / 4) - int(fRandom() * 4);
int tg = graphics.titlebg.g - (help.glow / 4) - int(fRandom() * 4);
int tb = graphics.titlebg.b - (help.glow / 4) - int(fRandom() * 4);
2020-01-01 21:29:24 +01:00
if (tr < 0) tr = 0;
if(tr>255) tr=255;
if (tg < 0) tg = 0;
if(tg>255) tg=255;
if (tb < 0) tb = 0;
if(tb>255) tb=255;
editormenurender(tr, tg, tb);
2020-01-01 21:29:24 +01:00
Make menus automatically centered and narrowed All menus had a hardcoded X position (offset to an arbitrary starting point of 110) and a hardcoded horizontal spacing for the "staircasing" (mostly 30 pixels, but for some specific menus hardcoded to 15, 20 or something else). Not all menus were centered, and seem to have been manually made narrower (with lower horizontal spacing) whenever text ran offscreen during development. This system may already be hard to work with in an English-only menu system, since you may need to adjust horizontal spacing or positioning when adding an option. The main reason I made this change is that it's even less optimal when menu options have to be translated, since maximum string lengths are hard to determine, and it's easy to have menu options running offscreen, especially when not all menus are checked for all languages and when options could be added in the middle of a menu after translations of that menu are already checked. Now, menus are automatically centered based on their options, and they are automatically made narrower if they won't fit with the default horizontal spacing of 30 pixels (with some padding). The game.menuxoff variable for the menu X position is now also offset to 0 instead of 110 The _default_ horizontal spacing can be changed on a per-menu basis, and most menus (not all) which already had a narrower spacing set, retain that as a maximum spacing, simply because they looked odd with 30 pixels of spacing (especially the main menu). They will be made even narrower automatically if needed. In the most extreme case, the spacing can go down to 0 and options will be displayed right below each other. This isn't in the usual style of the game, but at least we did the best we could to prevent options running offscreen. The only exception to automatic menu centering and narrowing is the list of player levels, because it's a special case and existing behavior would be better than automatic centering there.
2020-06-29 02:09:52 +02:00
graphics.drawmenu(tr, tg, tb);
2020-01-01 21:29:24 +01:00
}
else if (ed.textmod)
2020-01-01 21:29:24 +01:00
{
FillRect(graphics.backBuffer, 0, 221, 320, 240, graphics.getRGB(32, 32, 32));
FillRect(graphics.backBuffer, 0, 222, 320, 240, graphics.getRGB(0, 0, 0));
graphics.Print(4, 224, ed.textdesc, 255, 255, 255, false);
std::string input = key.keybuffer;
if (ed.entframe < 2)
2020-01-01 21:29:24 +01:00
{
input += "_";
2020-01-01 21:29:24 +01:00
}
else
{
input += " ";
2020-01-01 21:29:24 +01:00
}
graphics.Print(4, 232, input, 196, 196, 255 - help.glow, true);
2020-01-01 21:29:24 +01:00
}
else if(ed.warpmod)
{
//placing warp token
FillRect(graphics.backBuffer, 0,221,320,240, graphics.getRGB(32,32,32));
FillRect(graphics.backBuffer, 0,222,320,240, graphics.getRGB(0,0,0));
graphics.Print(4, 224, "Left click to place warp destination", 196, 196, 255 - help.glow, false);
graphics.Print(4, 232, "Right click to cancel", 196, 196, 255 - help.glow, false);
2020-01-01 21:29:24 +01:00
}
else
{
if(ed.spacemod)
{
FillRect(graphics.backBuffer, 0,208,320,240, graphics.getRGB(32,32,32));
FillRect(graphics.backBuffer, 0,209,320,240, graphics.getRGB(0,0,0));
2020-01-01 21:29:24 +01:00
//Draw little icons for each thingy
int tx=6, ty=211, tg=32;
if(ed.spacemenu==0)
{
for(int i=0; i<10; i++)
{
FillRect(graphics.backBuffer, 4+(i*tg), 209,20,20,graphics.getRGB(32,32,32));
2020-01-01 21:29:24 +01:00
}
FillRect(graphics.backBuffer, 4+(ed.drawmode*tg), 209,20,20,graphics.getRGB(64,64,64));
2020-01-01 21:29:24 +01:00
//0:
graphics.drawtile(tx,ty,83);
graphics.drawtile(tx+8,ty,83);
graphics.drawtile(tx,ty+8,83);
graphics.drawtile(tx+8,ty+8,83);
2020-01-01 21:29:24 +01:00
//1:
tx+=tg;
graphics.drawtile(tx,ty,680);
graphics.drawtile(tx+8,ty,680);
graphics.drawtile(tx,ty+8,680);
graphics.drawtile(tx+8,ty+8,680);
2020-01-01 21:29:24 +01:00
//2:
tx+=tg;
graphics.drawtile(tx+4,ty+4,8);
2020-01-01 21:29:24 +01:00
//3:
tx+=tg;
graphics.drawsprite(tx,ty,22,196,196,196);
2020-01-01 21:29:24 +01:00
//4:
tx+=tg;
graphics.drawsprite(tx,ty,21,196,196,196);
2020-01-01 21:29:24 +01:00
//5:
tx+=tg;
graphics.drawtile(tx,ty+4,3);
graphics.drawtile(tx+8,ty+4,4);
2020-01-01 21:29:24 +01:00
//6:
tx+=tg;
graphics.drawtile(tx,ty+4,24);
graphics.drawtile(tx+8,ty+4,24);
2020-01-01 21:29:24 +01:00
//7:
tx+=tg;
graphics.drawtile(tx,ty+4,1);
graphics.drawtile(tx+8,ty+4,1);
2020-01-01 21:29:24 +01:00
//8:
tx+=tg;
graphics.drawsprite(tx,ty,78+ed.entframe,196,196,196);
2020-01-01 21:29:24 +01:00
//9:
tx+=tg;
FillRect(graphics.backBuffer, tx+2,ty+8,12,1,graphics.getRGB(255,255,255));
2020-01-01 21:29:24 +01:00
for(int i=0; i<9; i++)
{
fillboxabs(4+(i*tg), 209,20,20,graphics.getRGB(96,96,96));
graphics.Print(22+(i*tg)-4, 225-4,help.String(i+1),164,164,164,false);
2020-01-01 21:29:24 +01:00
}
if(ed.drawmode==9)graphics.Print(22+(ed.drawmode*tg)-4, 225-4,"0",255,255,255,false);
2020-01-01 21:29:24 +01:00
fillboxabs(4+(9*tg), 209,20,20,graphics.getRGB(96,96,96));
graphics.Print(22+(9*tg)-4, 225-4, "0",164,164,164,false);
2020-01-01 21:29:24 +01:00
fillboxabs(4+(ed.drawmode*tg), 209,20,20,graphics.getRGB(200,200,200));
2020-01-01 21:29:24 +01:00
if(ed.drawmode<9)
{
graphics.Print(22+(ed.drawmode*tg)-4, 225-4,help.String(ed.drawmode+1),255,255,255,false);
2020-01-01 21:29:24 +01:00
}
graphics.Print(4, 232, "1/2", 196, 196, 255 - help.glow, false);
2020-01-01 21:29:24 +01:00
}
else
{
for(int i=0; i<7; i++)
{
FillRect(graphics.backBuffer, 4+(i*tg), 209,20,20,graphics.getRGB(32,32,32));
2020-01-01 21:29:24 +01:00
}
FillRect(graphics.backBuffer, 4+((ed.drawmode-10)*tg), 209,20,20,graphics.getRGB(64,64,64));
2020-01-01 21:29:24 +01:00
//10:
graphics.Print(tx,ty,"A",196, 196, 255 - help.glow, false);
graphics.Print(tx+8,ty,"B",196, 196, 255 - help.glow, false);
graphics.Print(tx,ty+8,"C",196, 196, 255 - help.glow, false);
graphics.Print(tx+8,ty+8,"D",196, 196, 255 - help.glow, false);
2020-01-01 21:29:24 +01:00
//11:
tx+=tg;
graphics.drawsprite(tx,ty,17,196,196,196);
2020-01-01 21:29:24 +01:00
//12:
tx+=tg;
fillboxabs(tx+4,ty+4,8,8,graphics.getRGB(96,96,96));
2020-01-01 21:29:24 +01:00
//13:
tx+=tg;
graphics.drawsprite(tx,ty,18+(ed.entframe%2),196,196,196);
2020-01-01 21:29:24 +01:00
//14:
tx+=tg;
FillRect(graphics.backBuffer, tx+6,ty+2,4,12,graphics.getRGB(255,255,255));
2020-01-01 21:29:24 +01:00
//15:
tx+=tg;
graphics.drawsprite(tx,ty,186,graphics.col_crewblue);
2020-01-01 21:29:24 +01:00
//16:
tx+=tg;
graphics.drawsprite(tx,ty,184,graphics.col_crewcyan);
if(ed.drawmode==10)graphics.Print(22+((ed.drawmode-10)*tg)-4, 225-4,"R",255,255,255,false);
if(ed.drawmode==11)graphics.Print(22+((ed.drawmode-10)*tg)-4, 225-4,"T",255,255,255,false);
if(ed.drawmode==12)graphics.Print(22+((ed.drawmode-10)*tg)-4, 225-4,"Y",255,255,255,false);
if(ed.drawmode==13)graphics.Print(22+((ed.drawmode-10)*tg)-4, 225-4,"U",255,255,255,false);
if(ed.drawmode==14)graphics.Print(22+((ed.drawmode-10)*tg)-4, 225-4,"I",255,255,255,false);
if(ed.drawmode==15)graphics.Print(22+((ed.drawmode-10)*tg)-4, 225-4,"O",255,255,255,false);
if(ed.drawmode==16)graphics.Print(22+((ed.drawmode-10)*tg)-4, 225-4,"P",255,255,255,false);
fillboxabs(4+(0*tg), 209,20,20,graphics.getRGB(96,96,96));
graphics.Print(22+(0*tg)-4, 225-4, "R",164,164,164,false);
fillboxabs(4+(1*tg), 209,20,20,graphics.getRGB(96,96,96));
graphics.Print(22+(1*tg)-4, 225-4, "T",164,164,164,false);
fillboxabs(4+(2*tg), 209,20,20,graphics.getRGB(96,96,96));
graphics.Print(22+(2*tg)-4, 225-4, "Y",164,164,164,false);
fillboxabs(4+(3*tg), 209,20,20,graphics.getRGB(96,96,96));
graphics.Print(22+(3*tg)-4, 225-4, "U",164,164,164,false);
fillboxabs(4+(4*tg), 209,20,20,graphics.getRGB(96,96,96));
graphics.Print(22+(4*tg)-4, 225-4, "I",164,164,164,false);
fillboxabs(4+(5*tg), 209,20,20,graphics.getRGB(96,96,96));
graphics.Print(22+(5*tg)-4, 225-4, "O",164,164,164,false);
fillboxabs(4+(6*tg), 209,20,20,graphics.getRGB(96,96,96));
graphics.Print(22+(6*tg)-4, 225-4, "P",164,164,164,false);
graphics.Print(4, 232, "2/2", 196, 196, 255 - help.glow, false);
2020-01-01 21:29:24 +01:00
}
graphics.Print(128, 232, "< and > keys change tool", 196, 196, 255 - help.glow, false);
2020-01-01 21:29:24 +01:00
FillRect(graphics.backBuffer, 0,198,120,10, graphics.getRGB(32,32,32));
FillRect(graphics.backBuffer, 0,199,119,9, graphics.getRGB(0,0,0));
2020-01-01 21:29:24 +01:00
switch(ed.drawmode)
{
case 0:
graphics.bprint(2,199, "1: Walls",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 1:
graphics.bprint(2,199, "2: Backing",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 2:
graphics.bprint(2,199, "3: Spikes",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 3:
graphics.bprint(2,199, "4: Trinkets",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 4:
graphics.bprint(2,199, "5: Checkpoint",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 5:
graphics.bprint(2,199, "6: Disappear",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 6:
graphics.bprint(2,199, "7: Conveyors",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 7:
graphics.bprint(2,199, "8: Moving",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 8:
graphics.bprint(2,199, "9: Enemies",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 9:
graphics.bprint(2,199, "0: Grav Line",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 10:
graphics.bprint(2,199, "R: Roomtext",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 11:
graphics.bprint(2,199, "T: Terminal",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 12:
graphics.bprint(2,199, "Y: Script Box",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 13:
graphics.bprint(2,199, "U: Warp Token",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 14:
graphics.bprint(2,199, "I: Warp Lines",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 15:
graphics.bprint(2,199, "O: Crewmate",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 16:
graphics.bprint(2,199, "P: Start Point",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
}
FillRect(graphics.backBuffer, 260,198,80,10, graphics.getRGB(32,32,32));
FillRect(graphics.backBuffer, 261,199,80,9, graphics.getRGB(0,0,0));
graphics.bprint(268,199, "("+help.String(ed.levx+1)+","+help.String(ed.levy+1)+")",196, 196, 255 - help.glow, false);
2020-01-01 21:29:24 +01:00
}
else
{
//FillRect(graphics.backBuffer, 0,230,72,240, graphics.RGB(32,32,32));
//FillRect(graphics.backBuffer, 0,231,71,240, graphics.RGB(0,0,0));
2020-01-01 21:29:24 +01:00
if(ed.level[ed.levx+(ed.maxwidth*ed.levy)].roomname!="")
{
if(ed.tiley<28)
{
if(ed.roomnamehide>0) ed.roomnamehide--;
}
else
{
if(ed.roomnamehide<12) ed.roomnamehide++;
}
if (graphics.translucentroomname)
{
graphics.footerrect.y = 230+ed.roomnamehide;
SDL_BlitSurface(graphics.footerbuffer, NULL, graphics.backBuffer, &graphics.footerrect);
}
else
{
FillRect(graphics.backBuffer, 0,230+ed.roomnamehide,320,10, graphics.getRGB(0,0,0));
}
graphics.bprint(5,231+ed.roomnamehide,ed.level[ed.levx+(ed.maxwidth*ed.levy)].roomname, 196, 196, 255 - help.glow, true);
graphics.bprint(4, 222, "SPACE ^ SHIFT ^", 196, 196, 255 - help.glow, false);
graphics.bprint(268,222, "("+help.String(ed.levx+1)+","+help.String(ed.levy+1)+")",196, 196, 255 - help.glow, false);
2020-01-01 21:29:24 +01:00
}
else
{
graphics.bprint(4, 232, "SPACE ^ SHIFT ^", 196, 196, 255 - help.glow, false);
graphics.bprint(268,232, "("+help.String(ed.levx+1)+","+help.String(ed.levy+1)+")",196, 196, 255 - help.glow, false);
2020-01-01 21:29:24 +01:00
}
}
if(ed.shiftmenu)
{
fillboxabs(0, 117,171+8,140,graphics.getRGB(64,64,64));
FillRect(graphics.backBuffer, 0,118,170+8,140, graphics.getRGB(0,0,0));
graphics.Print(4, 120, "F1: Change Tileset",164,164,164,false);
graphics.Print(4, 130, "F2: Change Colour",164,164,164,false);
graphics.Print(4, 140, "F3: Change Enemies",164,164,164,false);
graphics.Print(4, 150, "F4: Enemy Bounds",164,164,164,false);
graphics.Print(4, 160, "F5: Platform Bounds",164,164,164,false);
graphics.Print(4, 180, "F9: Reload Resources",164,164,164,false);
graphics.Print(4, 190, "F10: Direct Mode",164,164,164,false);
graphics.Print(4, 210, "W: Change Warp Dir",164,164,164,false);
graphics.Print(4, 220, "E: Change Roomname",164,164,164,false);
fillboxabs(220, 207,100,60,graphics.getRGB(64,64,64));
FillRect(graphics.backBuffer, 221,208,160,60, graphics.getRGB(0,0,0));
graphics.Print(224, 210, "S: Save Map",164,164,164,false);
graphics.Print(224, 220, "L: Load Map",164,164,164,false);
2020-01-01 21:29:24 +01:00
}
}
if(!ed.settingsmod && !ed.scripteditmod)
{
//Same as above, without borders
switch(ed.drawmode)
{
case 0:
graphics.bprint(2,2, "1: Walls",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 1:
graphics.bprint(2,2, "2: Backing",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 2:
graphics.bprint(2,2, "3: Spikes",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 3:
graphics.bprint(2,2, "4: Trinkets",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 4:
graphics.bprint(2,2, "5: Checkpoint",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 5:
graphics.bprint(2,2, "6: Disappear",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 6:
graphics.bprint(2,2, "7: Conveyors",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 7:
graphics.bprint(2,2, "8: Moving",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 8:
graphics.bprint(2,2, "9: Enemies",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 9:
graphics.bprint(2,2, "0: Grav Line",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 10:
graphics.bprint(2,2, "R: Roomtext",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 11:
graphics.bprint(2,2, "T: Terminal",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 12:
graphics.bprint(2,2, "Y: Script Box",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 13:
graphics.bprint(2,2, "U: Warp Token",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 14:
graphics.bprint(2,2, "I: Warp Lines",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 15:
graphics.bprint(2,2, "O: Crewmate",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
case 16:
graphics.bprint(2,2, "P: Start Point",196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
}
}
if(ed.notedelay>0 || ed.oldnotedelay>0)
2020-01-01 21:29:24 +01:00
{
float alpha = graphics.lerp(ed.oldnotedelay, ed.notedelay);
FillRect(graphics.backBuffer, 0,115,320,18, graphics.getRGB(92,92,92));
FillRect(graphics.backBuffer, 0,116,320,16, graphics.getRGB(0,0,0));
graphics.Print(0,121, ed.note,196-((45.0f-alpha)*4), 196-((45.0f-alpha)*4), 196-((45.0f-alpha)*4), true);
2020-01-01 21:29:24 +01:00
}
graphics.drawfade();
2020-01-01 21:29:24 +01:00
graphics.render();
2020-01-01 21:29:24 +01:00
}
void editorrenderfixed()
2020-01-01 21:29:24 +01:00
{
extern editorclass ed;
graphics.updatetitlecolours();
game.customcol=ed.getlevelcol(ed.levx+(ed.levy*ed.maxwidth))+1;
ed.entcol=ed.getenemycol(game.customcol);
graphics.setcol(ed.entcol);
ed.entcolreal = graphics.ct.colour;
2020-01-01 21:29:24 +01:00
if (game.ghostsenabled)
{
for (size_t i = 0; i < ed.ghosts.size(); i++)
{
GhostInfo& ghost = ed.ghosts[i];
if ((int) i > ed.currentghosts || ghost.rx != ed.levx || ghost.ry != ed.levy)
{
continue;
}
graphics.setcol(ghost.col);
ghost.realcol = graphics.ct.colour;
}
if (ed.currentghosts + 1 < (int)ed.ghosts.size()) {
ed.currentghosts++;
if (ed.zmod) ed.currentghosts++;
} else {
ed.currentghosts = (int)ed.ghosts.size() - 1;
}
}
if (!ed.settingsmod)
{
switch(ed.level[ed.levx+(ed.levy*ed.maxwidth)].warpdir)
{
case 1:
graphics.rcol=ed.getwarpbackground(ed.levx, ed.levy);
graphics.updatebackground(3);
break;
case 2:
graphics.rcol=ed.getwarpbackground(ed.levx, ed.levy);
graphics.updatebackground(4);
break;
case 3:
graphics.rcol=ed.getwarpbackground(ed.levx, ed.levy);
graphics.updatebackground(5);
break;
default:
break;
}
}
else if (!game.colourblindmode)
{
graphics.updatetowerbackground(graphics.titlebg);
}
}
void editorlogic()
{
extern editorclass ed;
//Misc
help.updateglow();
if (game.shouldreturntoeditor)
{
game.shouldreturntoeditor = false;
}
graphics.titlebg.bypos -= 2;
graphics.titlebg.bscroll = -2;
ed.entframedelay--;
if(ed.entframedelay<=0)
{
ed.entframe=(ed.entframe+1)%4;
ed.entframedelay=8;
}
ed.oldnotedelay = ed.notedelay;
if(ed.notedelay>0)
{
ed.notedelay--;
}
if (graphics.fademode == 1)
2020-01-01 21:29:24 +01:00
{
//Return to game
map.nexttowercolour();
graphics.titlebg.colstate = 10;
game.gamestate = TITLEMODE;
script.hardreset();
graphics.fademode = 4;
music.haltdasmusik();
FILESYSTEM_unmountassets(); // should be before music.play(6)
2020-01-01 21:29:24 +01:00
music.play(6);
map.nexttowercolour();
ed.settingsmod=false;
graphics.backgrounddrawn=false;
game.returntomenu(Menu::playerworlds);
2020-01-01 21:29:24 +01:00
}
}
static void editormenuactionpress()
{
extern editorclass ed;
switch (game.currentmenuname)
{
case Menu::ed_desc:
switch (game.currentmenuoption)
{
case 0:
ed.titlemod=true;
key.enabletextentry();
key.keybuffer=EditorData::GetInstance().title;
break;
case 1:
ed.creatormod=true;
key.enabletextentry();
key.keybuffer=EditorData::GetInstance().creator;
break;
case 2:
ed.desc1mod=true;
key.enabletextentry();
key.keybuffer=ed.Desc1;
break;
case 3:
ed.websitemod=true;
key.enabletextentry();
key.keybuffer=ed.website;
break;
case 4:
music.playef(11);
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::ed_settings:
switch (game.currentmenuoption)
{
case 0:
//Change level description stuff
music.playef(11);
game.createmenu(Menu::ed_desc);
map.nexttowercolour();
break;
case 1:
//Enter script editormode
music.playef(11);
ed.scripteditmod=true;
ed.clearscriptbuffer();
key.keybuffer="";
ed.hookmenupage=0;
ed.hookmenu=0;
ed.scripthelppage=0;
ed.scripthelppagedelay=0;
ed.sby=0;
ed.sbx=0, ed.pagey=0;
break;
case 2:
music.playef(11);
game.createmenu(Menu::ed_music);
map.nexttowercolour();
if(ed.levmusic>0) music.play(ed.levmusic);
break;
case 3:
music.playef(11);
game.ghostsenabled = !game.ghostsenabled;
break;
case 4:
//Load level
ed.settingsmod=false;
graphics.backgrounddrawn=false;
map.nexttowercolour();
ed.keydelay = 6;
ed.getlin(TEXT_LOAD, "Enter map filename to load:", &(ed.filename));
game.mapheld=true;
graphics.backgrounddrawn=false;
break;
case 5:
//Save level
ed.settingsmod=false;
map.nexttowercolour();
ed.keydelay = 6;
ed.getlin(TEXT_SAVE, "Enter map filename to save as:", &(ed.filename));
game.mapheld=true;
graphics.backgrounddrawn=false;
break;
case 6:
music.playef(11);
game.createmenu(Menu::ed_quit);
map.nexttowercolour();
break;
}
break;
case Menu::ed_music:
switch (game.currentmenuoption)
{
case 0:
ed.levmusic++;
if(ed.levmusic==16) ed.levmusic=0;
if(ed.levmusic>0)
{
music.play(ed.levmusic);
}
else
{
music.haltdasmusik();
}
music.playef(11);
break;
case 1:
music.playef(11);
music.fadeout();
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::ed_quit:
switch (game.currentmenuoption)
{
case 0:
//Saving and quit
ed.saveandquit=true;
ed.settingsmod=false;
map.nexttowercolour();
ed.keydelay = 6;
ed.getlin(TEXT_SAVE, "Enter map filename to save as:", &(ed.filename));
game.mapheld=true;
graphics.backgrounddrawn=false;
break;
case 1:
//Quit without saving
music.playef(11);
music.fadeout();
graphics.fademode = 2;
break;
case 2:
//Go back to editor
music.playef(11);
game.returnmenu();
map.nexttowercolour();
break;
}
break;
default:
break;
}
}
void editorinput()
2020-01-01 21:29:24 +01:00
{
extern editorclass ed;
2020-01-01 21:29:24 +01:00
game.mx = (float) key.mx;
game.my = (float) key.my;
ed.tilex=(game.mx - (game.mx%8))/8;
ed.tiley=(game.my - (game.my%8))/8;
if (graphics.screenbuffer->stretchMode == 1) {
// In this mode specifically, we have to fix the mouse coordinates
int winwidth, winheight;
graphics.screenbuffer->GetWindowSize(&winwidth, &winheight);
ed.tilex = ed.tilex * 320 / winwidth;
ed.tiley = ed.tiley * 240 / winheight;
}
2020-01-01 21:29:24 +01:00
bool up_pressed = key.isDown(SDLK_UP) || key.isDown(SDL_CONTROLLER_BUTTON_DPAD_UP);
bool down_pressed = key.isDown(SDLK_DOWN) || key.isDown(SDL_CONTROLLER_BUTTON_DPAD_DOWN);
bool left_pressed = key.isDown(SDLK_LEFT) || key.isDown(SDL_CONTROLLER_BUTTON_DPAD_LEFT);
bool right_pressed = key.isDown(SDLK_RIGHT) || key.isDown(SDL_CONTROLLER_BUTTON_DPAD_RIGHT);
2020-01-01 21:29:24 +01:00
game.press_left = false;
game.press_right = false;
game.press_action = false;
game.press_map = false;
if (key.isDown(KEYBOARD_LEFT) || key.isDown(KEYBOARD_a) || key.controllerWantsLeft(false))
2020-01-01 21:29:24 +01:00
{
game.press_left = true;
}
if (key.isDown(KEYBOARD_RIGHT) || key.isDown(KEYBOARD_d) || key.controllerWantsRight(false))
2020-01-01 21:29:24 +01:00
{
game.press_right = true;
}
if (key.isDown(KEYBOARD_z) || key.isDown(KEYBOARD_SPACE) || key.isDown(KEYBOARD_v) || key.isDown(game.controllerButton_flip))
2020-01-01 21:29:24 +01:00
{
game.press_action = true;
};
if (key.keymap[SDLK_F9] && (ed.keydelay==0)) {
ed.keydelay = 30;
ed.note="Reloaded resources";
ed.notedelay=45;
graphics.reloadresources();
}
2020-01-01 21:29:24 +01:00
if (key.isDown(KEYBOARD_ENTER)) game.press_map = true;
if (key.isDown(27) && !ed.settingskey)
{
ed.settingskey=true;
if (ed.textmod)
{
key.disabletextentry();
if (ed.textmod >= FIRST_ENTTEXT && ed.textmod <= LAST_ENTTEXT)
{
*ed.textptr = ed.oldenttext;
if (ed.oldenttext == "")
{
removeedentity(ed.textent);
}
}
ed.textmod = TEXT_NONE;
ed.shiftmenu = false;
ed.shiftkey = false;
}
Axe manual state trackers and use SDL_IsTextInputActive() After looking at pull request #446, I got a bit annoyed that we have TWO variables, key.textentrymode and ed.textentry, that we rolled ourselves to track the state of something SDL already provides us a function to easily query: SDL_IsTextInputActive(). We don't need to have either of these two variables, and we shouldn't. So that's what I do in this patch. Both variables have been axed in favor of using this function, and I just made a wrapper out of it, named key.textentry(). For bonus points, this gets rid of the ugly NO_CUSTOM_LEVELS and NO_EDITOR ifdef in main.cpp, since text entry is enabled when entering the script list and disabled when exiting it. This makes the code there easier to read, too. Furthermore, apparently key.textentrymode was initialized to *true* instead of false... for whatever reason. But that's gone now, too. Now, you'd think there wouldn't be any downside to using SDL_IsTextInputActive(). After all, it's a function that SDL itself provides, right? Wrong. For whatever reason, it seems like text input is active *from the start of the program*, meaning that what would happen is I would go into the editor, and find that I can't move around nor place tiles nor anything else. Then I would press Esc, and then suddenly become able to do those things I wanted to do before. I have no idea why the above happens, but all I can do is to just insert an SDL_StopTextInput() immediately after the SDL_Init() in main.cpp. Of course, I have to surround it with an SDL_IsTextInputActive() check to make sure I don't do anything extraneous by stopping input when it's already stopped.
2020-08-13 08:43:25 +02:00
else if (key.textentry())
2020-01-01 21:29:24 +01:00
{
key.disabletextentry();
ed.titlemod=false;
ed.desc1mod=false;
ed.desc2mod=false;
ed.desc3mod=false;
ed.websitemod=false;
ed.creatormod=false;
ed.shiftmenu=false;
ed.shiftkey=false;
}
else if(ed.boundarymod>0)
{
ed.boundarymod=0;
}
else
{
ed.settingsmod=!ed.settingsmod;
graphics.backgrounddrawn=false;
2020-01-01 21:29:24 +01:00
if (ed.settingsmod)
{
bool edsettings_in_stack = false;
for (size_t i = 0; i < game.menustack.size(); i++)
{
if (game.menustack[i].name == Menu::ed_settings)
{
edsettings_in_stack = true;
break;
}
}
if (edsettings_in_stack)
{
game.returntomenu(Menu::ed_settings);
}
else
{
game.createmenu(Menu::ed_settings);
}
map.nexttowercolour();
}
2020-01-01 21:29:24 +01:00
}
}
if (!key.isDown(27))
{
ed.settingskey=false;
}
if(key.keymap[SDLK_LCTRL] || key.keymap[SDLK_RCTRL])
{
if(key.leftbutton) key.rightbutton=true;
}
if(ed.scripteditmod)
{
if(ed.scripthelppage==0)
{
//hook select menu
if(ed.keydelay>0) ed.keydelay--;
if(up_pressed && ed.keydelay<=0)
2020-01-01 21:29:24 +01:00
{
ed.keydelay=6;
ed.hookmenu--;
}
if(down_pressed && ed.keydelay<=0)
2020-01-01 21:29:24 +01:00
{
ed.keydelay=6;
ed.hookmenu++;
}
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
if(ed.hookmenu>=(int)ed.hooklist.size())
2020-01-01 21:29:24 +01:00
{
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
ed.hookmenu=ed.hooklist.size()-1;
2020-01-01 21:29:24 +01:00
}
if(ed.hookmenu<0) ed.hookmenu=0;
if(ed.hookmenu<ed.hookmenupage)
{
ed.hookmenupage=ed.hookmenu;
}
if(ed.hookmenu>=ed.hookmenupage+9)
{
ed.hookmenupage=ed.hookmenu+8;
}
if(!key.keymap[SDLK_BACKSPACE]) ed.deletekeyheld=0;
if(key.keymap[SDLK_BACKSPACE] && ed.deletekeyheld==0 && !ed.hooklist.empty())
2020-01-01 21:29:24 +01:00
{
ed.deletekeyheld=1;
music.playef(2);
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
ed.removehook(ed.hooklist[(ed.hooklist.size()-1)-ed.hookmenu]);
2020-01-01 21:29:24 +01:00
}
if (!game.press_action && !game.press_left && !game.press_right
&& !up_pressed && !down_pressed && !key.isDown(27)) game.jumpheld = false;
2020-01-01 21:29:24 +01:00
if (!game.jumpheld)
{
if (game.press_action || game.press_left || game.press_right || game.press_map
|| up_pressed || down_pressed || key.isDown(27))
2020-01-01 21:29:24 +01:00
{
game.jumpheld = true;
}
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
if ((game.press_action || game.press_map) && !ed.hooklist.empty())
2020-01-01 21:29:24 +01:00
{
game.mapheld=true;
ed.scripthelppage=1;
key.enabletextentry();
2020-01-01 21:29:24 +01:00
key.keybuffer="";
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
ed.sbscript=ed.hooklist[(ed.hooklist.size()-1)-ed.hookmenu];
2020-01-01 21:29:24 +01:00
ed.loadhookineditor(ed.sbscript);
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
ed.sby=ed.sb.size()-1;
2020-01-01 21:29:24 +01:00
ed.pagey=0;
while(ed.sby>=20)
{
ed.pagey++;
ed.sby--;
}
key.keybuffer=ed.sb[ed.pagey+ed.sby];
ed.sbx = utf8::unchecked::distance(ed.sb[ed.pagey+ed.sby].begin(), ed.sb[ed.pagey+ed.sby].end());
2020-01-01 21:29:24 +01:00
}
if (key.isDown(27))
{
ed.scripteditmod=false;
ed.settingsmod=false;
}
}
}
else if(ed.scripthelppage==1)
{
//Script editor!
if (key.isDown(27))
{
ed.scripthelppage=0;
game.jumpheld = true;
//save the script for use again!
ed.addhook(ed.sbscript);
}
if(ed.keydelay>0) ed.keydelay--;
if(up_pressed && ed.keydelay<=0)
2020-01-01 21:29:24 +01:00
{
ed.keydelay=6;
ed.sby--;
if(ed.sby<=5)
{
if(ed.pagey>0)
{
ed.pagey--;
ed.sby++;
}
else
{
if(ed.sby<0) ed.sby=0;
}
}
key.keybuffer=ed.sb[ed.pagey+ed.sby];
}
if(down_pressed && ed.keydelay<=0)
2020-01-01 21:29:24 +01:00
{
ed.keydelay=6;
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
if(ed.sby+ed.pagey<(int)ed.sb.size()-1)
2020-01-01 21:29:24 +01:00
{
ed.sby++;
if(ed.sby>=20)
{
ed.pagey++;
ed.sby--;
}
}
key.keybuffer=ed.sb[ed.pagey+ed.sby];
}
Fix frame-ordering backspacing empty line bug in script editor There is a long-standing bug with the script editor where if you delete the last character of a line, it IMMEDIATELY deletes the line you're on, and then moves your cursor back to the previous line. This is annoying, to say the least. The reason for this is that, in the sequence of events that happens in one frame (known as frame ordering), the code that backspaces one character from the line when you press Backspace is ran BEFORE the code to remove an empty line if you backspace it is ran. The former is located in key.Poll(), and the latter is located in editorinput(). Thus, when you press Backspace, the game first runs key.Poll(), sees that you've pressed Backspace, and dutifully removes the last character from a line. The line is now empty. Then, when the game gets around to the "Are you pressing Backspace on an empty line?" check in editorinput(), it thinks that you're pressing Backspace on an empty line, and then does the usual line-removing stuff. And actually, when it does the check in editorinput(), it ACTUALLY asks "Are you pressing Backspace on THIS frame and was the line empty LAST frame?" because it's checking against its own copy of the input buffer, before copying the input buffer to its own local copy. So the problem only happens if you press and hold Backspace for more than 1 frame. It's a small consolation prize for this annoyance, getting to tap-tap-tap Backspace in the hopes that you only press it for 1 frame, while in the middle of something more important to do like, oh I don't know, writing a script. So there are two potential solutions here: (1) Just change the frame ordering around. This is risky to say the least, because I'm not sure what behavior depends on exactly which frame order. It's not like it's key.Poll() and then IMMEDIATELY afterwards editorinput() is run, it's more like key.Poll(), some things that obviously depend on key.Poll() running before them, and THEN editorinput(). Also, editorinput() is only one possible thing that could be ran afterwards, on the next frame we could be running something else entirely instead. (2) Add a kludge variable to signal when the line is ALREADY empty so the game doesn't re-check the already-empty line and conclude that you're already immediately backspacing an empty line. I went with (2) for this commit, and I've added the kludge variable key.linealreadyemptykludge. However, that by itself isn't enough to fix it. It only adds about a frame or so of delay before the game goes right back to saying "Oh, you're ALREADY somehow pressing backspace again? I'll just delete this line real quick" and the behavior is basically the same as before, except now you have to hit Backspace for TWO frames or less instead of one in order to not have it happen. What we need is to have a delay set as well, when the game deletes the last line of a char. So I set ed.keydelay to 6 as well if editorinput() sses that key.linealreadyemptykludge is on.
2020-01-19 03:17:46 +01:00
if(key.linealreadyemptykludge)
{
ed.keydelay=6;
key.linealreadyemptykludge=false;
}
if(key.pressedbackspace && ed.sb[ed.pagey+ed.sby]=="" && ed.keydelay<=0)
2020-01-01 21:29:24 +01:00
{
//Remove this line completely
ed.removeline(ed.pagey+ed.sby);
ed.sby--;
if(ed.sby<=5)
{
if(ed.pagey>0)
{
ed.pagey--;
ed.sby++;
}
else
{
if(ed.sby<0) ed.sby=0;
}
}
key.keybuffer=ed.sb[ed.pagey+ed.sby];
ed.keydelay=6;
2020-01-01 21:29:24 +01:00
}
// Remove all pipes, they are the line separator in the XML
key.keybuffer.erase(std::remove(key.keybuffer.begin(), key.keybuffer.end(), '|'), key.keybuffer.end());
2020-01-01 21:29:24 +01:00
ed.sb[ed.pagey+ed.sby]=key.keybuffer;
ed.sbx = utf8::unchecked::distance(ed.sb[ed.pagey+ed.sby].begin(), ed.sb[ed.pagey+ed.sby].end());
2020-01-01 21:29:24 +01:00
if(!game.press_map && !key.isDown(27)) game.mapheld=false;
if (!game.mapheld)
{
if(game.press_map)
{
game.mapheld=true;
//Continue to next line
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
if(ed.sby+ed.pagey>=(int)ed.sb.size()) //we're on the last line
2020-01-01 21:29:24 +01:00
{
ed.sby++;
if(ed.sby>=20)
{
ed.pagey++;
ed.sby--;
}
key.keybuffer=ed.sb[ed.pagey+ed.sby];
ed.sbx = utf8::unchecked::distance(ed.sb[ed.pagey+ed.sby].begin(), ed.sb[ed.pagey+ed.sby].end());
2020-01-01 21:29:24 +01:00
}
else
{
//We're not, insert a line instead
ed.sby++;
if(ed.sby>=20)
{
ed.pagey++;
ed.sby--;
}
ed.insertline(ed.sby+ed.pagey);
key.keybuffer="";
ed.sbx = 0;
}
}
}
}
}
else if (ed.textmod)
2020-01-01 21:29:24 +01:00
{
*ed.textptr = key.keybuffer;
if (!game.press_map && !key.isDown(27))
2020-01-01 21:29:24 +01:00
{
game.mapheld = false;
2020-01-01 21:29:24 +01:00
}
if (!game.mapheld && game.press_map)
2020-01-01 21:29:24 +01:00
{
game.mapheld = true;
key.disabletextentry();
switch (ed.textmod)
{
case TEXT_GOTOROOM:
{
std::vector<std::string> coords = split(key.keybuffer, ',');
if (coords.size() != 2)
{
ed.note = "[ ERROR: Invalid format ]";
ed.notedelay = 45;
break;
}
ed.levx = clamp(help.Int(coords[0].c_str()) - 1, 0, ed.mapwidth - 1);
ed.levy = clamp(help.Int(coords[1].c_str()) - 1, 0, ed.mapheight - 1);
graphics.backgrounddrawn = false;
break;
}
case TEXT_LOAD:
{
std::string loadstring = ed.filename + ".vvvvvv";
if (ed.load(loadstring))
{
// don't use filename, it has the full path
char buffer[64];
SDL_snprintf(buffer, sizeof(buffer), "[ Loaded map: %s.vvvvvv ]", ed.filename.c_str());
ed.note = buffer;
}
else
{
ed.note = "[ ERROR: Could not load level ]";
}
ed.notedelay = 45;
break;
}
case TEXT_SAVE:
{
std::string savestring = ed.filename + ".vvvvvv";
if (ed.save(savestring))
{
char buffer[64];
SDL_snprintf(buffer, sizeof(buffer), "[ Saved map: %s.vvvvvv ]", ed.filename.c_str());
ed.note = buffer;
}
else
{
ed.note = "[ ERROR: Could not save level! ]";
ed.saveandquit = false;
}
ed.notedelay = 45;
if (ed.saveandquit)
{
graphics.fademode = 2; // quit editor
}
break;
}
case TEXT_SCRIPT:
ed.clearscriptbuffer();
if (!ed.checkhook(key.keybuffer))
{
ed.addhook(key.keybuffer);
}
break;
default:
break;
}
ed.shiftmenu = false;
ed.shiftkey = false;
ed.textmod = TEXT_NONE;
2020-01-01 21:29:24 +01:00
}
}
Axe manual state trackers and use SDL_IsTextInputActive() After looking at pull request #446, I got a bit annoyed that we have TWO variables, key.textentrymode and ed.textentry, that we rolled ourselves to track the state of something SDL already provides us a function to easily query: SDL_IsTextInputActive(). We don't need to have either of these two variables, and we shouldn't. So that's what I do in this patch. Both variables have been axed in favor of using this function, and I just made a wrapper out of it, named key.textentry(). For bonus points, this gets rid of the ugly NO_CUSTOM_LEVELS and NO_EDITOR ifdef in main.cpp, since text entry is enabled when entering the script list and disabled when exiting it. This makes the code there easier to read, too. Furthermore, apparently key.textentrymode was initialized to *true* instead of false... for whatever reason. But that's gone now, too. Now, you'd think there wouldn't be any downside to using SDL_IsTextInputActive(). After all, it's a function that SDL itself provides, right? Wrong. For whatever reason, it seems like text input is active *from the start of the program*, meaning that what would happen is I would go into the editor, and find that I can't move around nor place tiles nor anything else. Then I would press Esc, and then suddenly become able to do those things I wanted to do before. I have no idea why the above happens, but all I can do is to just insert an SDL_StopTextInput() immediately after the SDL_Init() in main.cpp. Of course, I have to surround it with an SDL_IsTextInputActive() check to make sure I don't do anything extraneous by stopping input when it's already stopped.
2020-08-13 08:43:25 +02:00
else if (key.textentry())
{
if(ed.titlemod)
2020-01-01 21:29:24 +01:00
{
EditorData::GetInstance().title=key.keybuffer;
}
else if(ed.creatormod)
{
EditorData::GetInstance().creator=key.keybuffer;
}
else if(ed.websitemod)
{
ed.website=key.keybuffer;
}
else if(ed.desc1mod)
{
ed.Desc1=key.keybuffer;
}
else if(ed.desc2mod)
{
ed.Desc2=key.keybuffer;
}
else if(ed.desc3mod)
{
ed.Desc3=key.keybuffer;
}
if(!game.press_map && !key.isDown(27)) game.mapheld=false;
if (!game.mapheld)
{
if(game.press_map)
{
game.mapheld=true;
if(ed.titlemod)
2020-01-01 21:29:24 +01:00
{
EditorData::GetInstance().title=key.keybuffer;
ed.titlemod=false;
}
else if(ed.creatormod)
{
EditorData::GetInstance().creator=key.keybuffer;
ed.creatormod=false;
}
else if(ed.websitemod)
{
ed.website=key.keybuffer;
ed.websitemod=false;
}
else if(ed.desc1mod)
{
ed.Desc1=key.keybuffer;
}
else if(ed.desc2mod)
{
ed.Desc2=key.keybuffer;
}
else if(ed.desc3mod)
{
ed.Desc3=key.keybuffer;
ed.desc3mod=false;
}
key.disabletextentry();
if(ed.desc1mod)
{
ed.desc1mod=false;
ed.desc2mod=true;
key.enabletextentry();
key.keybuffer=ed.Desc2;
}
else if(ed.desc2mod)
{
ed.desc2mod=false;
ed.desc3mod=true;
key.enabletextentry();
key.keybuffer=ed.Desc3;
}
}
}
}
else
{
if(ed.settingsmod)
{
if (!game.press_action && !game.press_left && !game.press_right
&& !up_pressed && !down_pressed) game.jumpheld = false;
2020-01-01 21:29:24 +01:00
if (!game.jumpheld)
{
if (game.press_action || game.press_left || game.press_right || game.press_map
|| up_pressed || down_pressed)
2020-01-01 21:29:24 +01:00
{
game.jumpheld = true;
}
if(game.menustart)
{
if (game.press_left || up_pressed)
2020-01-01 21:29:24 +01:00
{
game.currentmenuoption--;
}
else if (game.press_right || down_pressed)
2020-01-01 21:29:24 +01:00
{
game.currentmenuoption++;
}
}
if (game.currentmenuoption < 0) game.currentmenuoption = game.menuoptions.size()-1;
if (game.currentmenuoption >= (int) game.menuoptions.size() ) game.currentmenuoption = 0;
2020-01-01 21:29:24 +01:00
if (game.press_action)
{
editormenuactionpress();
2020-01-01 21:29:24 +01:00
}
}
}
else if (ed.keydelay > 0)
{
ed.keydelay--;
}
else if (key.keymap[SDLK_LCTRL] || key.keymap[SDLK_RCTRL])
{
// Ctrl modifiers
ed.dmtileeditor=10;
if(left_pressed)
{
ed.dmtile--;
ed.keydelay=3;
if(ed.dmtile<0) ed.dmtile+=1200;
}
else if(right_pressed)
{
ed.dmtile++;
ed.keydelay=3;
if(ed.dmtile>=1200) ed.dmtile-=1200;
}
if(up_pressed)
{
ed.dmtile-=40;
ed.keydelay=3;
if(ed.dmtile<0) ed.dmtile+=1200;
}
else if(down_pressed)
{
ed.dmtile+=40;
ed.keydelay=3;
if(ed.dmtile>=1200) ed.dmtile-=1200;
}
}
else if (key.keymap[SDLK_LSHIFT] || key.keymap[SDLK_RSHIFT])
{
// Shift modifiers
if (key.keymap[SDLK_F1])
{
ed.switch_tileset(true);
graphics.backgrounddrawn = false;
ed.keydelay = 6;
}
if (key.keymap[SDLK_F2])
{
ed.switch_tilecol(true);
graphics.backgrounddrawn = false;
ed.keydelay = 6;
}
if (key.keymap[SDLK_F3])
{
ed.switch_enemy(true);
ed.keydelay=6;
}
if (up_pressed || down_pressed || left_pressed || right_pressed)
{
ed.keydelay=6;
if(up_pressed)
{
ed.mapheight--;
}
else if(down_pressed)
{
ed.mapheight++;
}
else if(left_pressed)
{
ed.mapwidth--;
}
else if(right_pressed)
{
ed.mapwidth++;
}
if(ed.mapwidth<1) ed.mapwidth=1;
if(ed.mapheight<1) ed.mapheight=1;
if(ed.mapwidth>=ed.maxwidth) ed.mapwidth=ed.maxwidth;
if(ed.mapheight>=ed.maxheight) ed.mapheight=ed.maxheight;
ed.note = "Mapsize is now [" + help.String(ed.mapwidth) + "," + help.String(ed.mapheight) + "]";
ed.notedelay=45;
}
if(!ed.shiftkey)
{
if(ed.shiftmenu)
{
ed.shiftmenu=false;
}
else
{
ed.shiftmenu=true;
}
}
ed.shiftkey=true;
}
2020-01-01 21:29:24 +01:00
else
{
// No modifiers
ed.shiftkey=false;
if(key.keymap[SDLK_F1])
2020-01-01 21:29:24 +01:00
{
ed.switch_tileset();
graphics.backgrounddrawn = false;
ed.keydelay = 6;
2020-01-01 21:29:24 +01:00
}
if(key.keymap[SDLK_F2])
2020-01-01 21:29:24 +01:00
{
ed.switch_tilecol();
graphics.backgrounddrawn = false;
ed.keydelay = 6;
2020-01-01 21:29:24 +01:00
}
if(key.keymap[SDLK_F3])
2020-01-01 21:29:24 +01:00
{
ed.switch_enemy();
2020-01-01 21:29:24 +01:00
ed.keydelay=6;
}
if(key.keymap[SDLK_F4])
2020-01-01 21:29:24 +01:00
{
ed.keydelay=6;
ed.boundarytype=1;
ed.boundarymod=1;
}
if(key.keymap[SDLK_F5])
2020-01-01 21:29:24 +01:00
{
ed.keydelay=6;
ed.boundarytype=2;
ed.boundarymod=1;
}
if(key.keymap[SDLK_F10])
2020-01-01 21:29:24 +01:00
{
if(ed.level[ed.levx+(ed.levy*ed.maxwidth)].directmode==1)
{
ed.level[ed.levx+(ed.levy*ed.maxwidth)].directmode=0;
ed.note="Direct Mode Disabled";
}
else
{
ed.level[ed.levx+(ed.levy*ed.maxwidth)].directmode=1;
ed.note="Direct Mode Enabled";
}
graphics.backgrounddrawn=false;
2020-01-01 21:29:24 +01:00
ed.notedelay=45;
ed.updatetiles=true;
ed.keydelay=6;
}
if(key.keymap[SDLK_1]) ed.drawmode=0;
if(key.keymap[SDLK_2]) ed.drawmode=1;
if(key.keymap[SDLK_3]) ed.drawmode=2;
if(key.keymap[SDLK_4]) ed.drawmode=3;
if(key.keymap[SDLK_5]) ed.drawmode=4;
if(key.keymap[SDLK_6]) ed.drawmode=5;
if(key.keymap[SDLK_7]) ed.drawmode=6;
if(key.keymap[SDLK_8]) ed.drawmode=7;
if(key.keymap[SDLK_9]) ed.drawmode=8;
if(key.keymap[SDLK_0]) ed.drawmode=9;
if(key.keymap[SDLK_r]) ed.drawmode=10;
if(key.keymap[SDLK_t]) ed.drawmode=11;
if(key.keymap[SDLK_y]) ed.drawmode=12;
if(key.keymap[SDLK_u]) ed.drawmode=13;
if(key.keymap[SDLK_i]) ed.drawmode=14;
if(key.keymap[SDLK_o]) ed.drawmode=15;
if(key.keymap[SDLK_p]) ed.drawmode=16;
if(key.keymap[SDLK_w])
2020-01-01 21:29:24 +01:00
{
Remove checks to prevent having both warp lines/BGs in editor It's perfectly acceptable to have both warp lines and a warping background in the same room. Many levels do this exact thing, I would say at least 30 or so levels, many of them popular and played by many, and this has never caused any issues at all. All that having both warp lines and warp BG does is make it so the warping of the warping background gets overriden by the warp lines, but make it so the background is still a warp background. So in effect, you can have a warp background without any warping. This is perfectly defined behavior. Except, for whatever reason, it's unintentional, and the editor tries to prevent you from doing it. Key word being "tries". The code to prevent having both warp types is bugged (at least when you change the warp BG. The check when you place warp lines seems to be solid). It compares the p1 and p2 attributes of warp lines to the x-coordinate and y-coordinate of the room, despite p1 and p2 having nothing to do with room coordinates. p1 is the type of the warp line and should be treated as an enum, and p2 is the offset of the warp line from the top/left of the screen. This results in this check sometimes working if you're unlucky, but never actually working properly most of the time. This means people can first place warp lines, and then change the warp background later, to have both warp lines and a warp background. Having these checks just further complicates the code, makes it more error-prone, and just inconveniences people when they want to do something that's perfectly fine to do. So it's best if we just remove these checks.
2020-09-06 05:02:27 +02:00
ed.level[ed.levx+(ed.levy*ed.maxwidth)].warpdir=(ed.level[ed.levx+(ed.levy*ed.maxwidth)].warpdir+1)%4;
if(ed.level[ed.levx+(ed.levy*ed.maxwidth)].warpdir==0)
2020-01-01 21:29:24 +01:00
{
Remove checks to prevent having both warp lines/BGs in editor It's perfectly acceptable to have both warp lines and a warping background in the same room. Many levels do this exact thing, I would say at least 30 or so levels, many of them popular and played by many, and this has never caused any issues at all. All that having both warp lines and warp BG does is make it so the warping of the warping background gets overriden by the warp lines, but make it so the background is still a warp background. So in effect, you can have a warp background without any warping. This is perfectly defined behavior. Except, for whatever reason, it's unintentional, and the editor tries to prevent you from doing it. Key word being "tries". The code to prevent having both warp types is bugged (at least when you change the warp BG. The check when you place warp lines seems to be solid). It compares the p1 and p2 attributes of warp lines to the x-coordinate and y-coordinate of the room, despite p1 and p2 having nothing to do with room coordinates. p1 is the type of the warp line and should be treated as an enum, and p2 is the offset of the warp line from the top/left of the screen. This results in this check sometimes working if you're unlucky, but never actually working properly most of the time. This means people can first place warp lines, and then change the warp background later, to have both warp lines and a warp background. Having these checks just further complicates the code, makes it more error-prone, and just inconveniences people when they want to do something that's perfectly fine to do. So it's best if we just remove these checks.
2020-09-06 05:02:27 +02:00
ed.note="Room warping disabled";
ed.notedelay=45;
graphics.backgrounddrawn=false;
2020-01-01 21:29:24 +01:00
}
Remove checks to prevent having both warp lines/BGs in editor It's perfectly acceptable to have both warp lines and a warping background in the same room. Many levels do this exact thing, I would say at least 30 or so levels, many of them popular and played by many, and this has never caused any issues at all. All that having both warp lines and warp BG does is make it so the warping of the warping background gets overriden by the warp lines, but make it so the background is still a warp background. So in effect, you can have a warp background without any warping. This is perfectly defined behavior. Except, for whatever reason, it's unintentional, and the editor tries to prevent you from doing it. Key word being "tries". The code to prevent having both warp types is bugged (at least when you change the warp BG. The check when you place warp lines seems to be solid). It compares the p1 and p2 attributes of warp lines to the x-coordinate and y-coordinate of the room, despite p1 and p2 having nothing to do with room coordinates. p1 is the type of the warp line and should be treated as an enum, and p2 is the offset of the warp line from the top/left of the screen. This results in this check sometimes working if you're unlucky, but never actually working properly most of the time. This means people can first place warp lines, and then change the warp background later, to have both warp lines and a warp background. Having these checks just further complicates the code, makes it more error-prone, and just inconveniences people when they want to do something that's perfectly fine to do. So it's best if we just remove these checks.
2020-09-06 05:02:27 +02:00
else if(ed.level[ed.levx+(ed.levy*ed.maxwidth)].warpdir==1)
2020-01-01 21:29:24 +01:00
{
Remove checks to prevent having both warp lines/BGs in editor It's perfectly acceptable to have both warp lines and a warping background in the same room. Many levels do this exact thing, I would say at least 30 or so levels, many of them popular and played by many, and this has never caused any issues at all. All that having both warp lines and warp BG does is make it so the warping of the warping background gets overriden by the warp lines, but make it so the background is still a warp background. So in effect, you can have a warp background without any warping. This is perfectly defined behavior. Except, for whatever reason, it's unintentional, and the editor tries to prevent you from doing it. Key word being "tries". The code to prevent having both warp types is bugged (at least when you change the warp BG. The check when you place warp lines seems to be solid). It compares the p1 and p2 attributes of warp lines to the x-coordinate and y-coordinate of the room, despite p1 and p2 having nothing to do with room coordinates. p1 is the type of the warp line and should be treated as an enum, and p2 is the offset of the warp line from the top/left of the screen. This results in this check sometimes working if you're unlucky, but never actually working properly most of the time. This means people can first place warp lines, and then change the warp background later, to have both warp lines and a warp background. Having these checks just further complicates the code, makes it more error-prone, and just inconveniences people when they want to do something that's perfectly fine to do. So it's best if we just remove these checks.
2020-09-06 05:02:27 +02:00
ed.note="Room warps horizontally";
2020-01-01 21:29:24 +01:00
ed.notedelay=45;
Remove checks to prevent having both warp lines/BGs in editor It's perfectly acceptable to have both warp lines and a warping background in the same room. Many levels do this exact thing, I would say at least 30 or so levels, many of them popular and played by many, and this has never caused any issues at all. All that having both warp lines and warp BG does is make it so the warping of the warping background gets overriden by the warp lines, but make it so the background is still a warp background. So in effect, you can have a warp background without any warping. This is perfectly defined behavior. Except, for whatever reason, it's unintentional, and the editor tries to prevent you from doing it. Key word being "tries". The code to prevent having both warp types is bugged (at least when you change the warp BG. The check when you place warp lines seems to be solid). It compares the p1 and p2 attributes of warp lines to the x-coordinate and y-coordinate of the room, despite p1 and p2 having nothing to do with room coordinates. p1 is the type of the warp line and should be treated as an enum, and p2 is the offset of the warp line from the top/left of the screen. This results in this check sometimes working if you're unlucky, but never actually working properly most of the time. This means people can first place warp lines, and then change the warp background later, to have both warp lines and a warp background. Having these checks just further complicates the code, makes it more error-prone, and just inconveniences people when they want to do something that's perfectly fine to do. So it's best if we just remove these checks.
2020-09-06 05:02:27 +02:00
graphics.backgrounddrawn=false;
2020-01-01 21:29:24 +01:00
}
Remove checks to prevent having both warp lines/BGs in editor It's perfectly acceptable to have both warp lines and a warping background in the same room. Many levels do this exact thing, I would say at least 30 or so levels, many of them popular and played by many, and this has never caused any issues at all. All that having both warp lines and warp BG does is make it so the warping of the warping background gets overriden by the warp lines, but make it so the background is still a warp background. So in effect, you can have a warp background without any warping. This is perfectly defined behavior. Except, for whatever reason, it's unintentional, and the editor tries to prevent you from doing it. Key word being "tries". The code to prevent having both warp types is bugged (at least when you change the warp BG. The check when you place warp lines seems to be solid). It compares the p1 and p2 attributes of warp lines to the x-coordinate and y-coordinate of the room, despite p1 and p2 having nothing to do with room coordinates. p1 is the type of the warp line and should be treated as an enum, and p2 is the offset of the warp line from the top/left of the screen. This results in this check sometimes working if you're unlucky, but never actually working properly most of the time. This means people can first place warp lines, and then change the warp background later, to have both warp lines and a warp background. Having these checks just further complicates the code, makes it more error-prone, and just inconveniences people when they want to do something that's perfectly fine to do. So it's best if we just remove these checks.
2020-09-06 05:02:27 +02:00
else if(ed.level[ed.levx+(ed.levy*ed.maxwidth)].warpdir==2)
2020-01-01 21:29:24 +01:00
{
Remove checks to prevent having both warp lines/BGs in editor It's perfectly acceptable to have both warp lines and a warping background in the same room. Many levels do this exact thing, I would say at least 30 or so levels, many of them popular and played by many, and this has never caused any issues at all. All that having both warp lines and warp BG does is make it so the warping of the warping background gets overriden by the warp lines, but make it so the background is still a warp background. So in effect, you can have a warp background without any warping. This is perfectly defined behavior. Except, for whatever reason, it's unintentional, and the editor tries to prevent you from doing it. Key word being "tries". The code to prevent having both warp types is bugged (at least when you change the warp BG. The check when you place warp lines seems to be solid). It compares the p1 and p2 attributes of warp lines to the x-coordinate and y-coordinate of the room, despite p1 and p2 having nothing to do with room coordinates. p1 is the type of the warp line and should be treated as an enum, and p2 is the offset of the warp line from the top/left of the screen. This results in this check sometimes working if you're unlucky, but never actually working properly most of the time. This means people can first place warp lines, and then change the warp background later, to have both warp lines and a warp background. Having these checks just further complicates the code, makes it more error-prone, and just inconveniences people when they want to do something that's perfectly fine to do. So it's best if we just remove these checks.
2020-09-06 05:02:27 +02:00
ed.note="Room warps vertically";
ed.notedelay=45;
graphics.backgrounddrawn=false;
}
else if(ed.level[ed.levx+(ed.levy*ed.maxwidth)].warpdir==3)
{
ed.note="Room warps in all directions";
ed.notedelay=45;
graphics.backgrounddrawn=false;
2020-01-01 21:29:24 +01:00
}
ed.keydelay=6;
}
if(key.keymap[SDLK_e])
2020-01-01 21:29:24 +01:00
{
ed.keydelay = 6;
ed.getlin(TEXT_ROOMNAME, "Enter new room name:", &(ed.level[ed.levx+(ed.levy*ed.maxwidth)].roomname));
2020-01-01 21:29:24 +01:00
game.mapheld=true;
}
if (key.keymap[SDLK_g])
{
ed.keydelay = 6;
ed.getlin(TEXT_GOTOROOM, "Enter room coordinates x,y:", NULL);
game.mapheld=true;
}
2020-01-01 21:29:24 +01:00
//Save and load
if(key.keymap[SDLK_s])
2020-01-01 21:29:24 +01:00
{
ed.keydelay = 6;
ed.getlin(TEXT_SAVE, "Enter map filename to save map as:", &(ed.filename));
2020-01-01 21:29:24 +01:00
game.mapheld=true;
}
if(key.keymap[SDLK_l])
2020-01-01 21:29:24 +01:00
{
ed.keydelay = 6;
ed.getlin(TEXT_LOAD, "Enter map filename to load:", &(ed.filename));
2020-01-01 21:29:24 +01:00
game.mapheld=true;
}
if(!game.press_map) game.mapheld=false;
if (!game.mapheld)
{
if(game.press_map)
{
game.mapheld=true;
//Ok! Scan the room for the closest checkpoint
int testeditor=-1;
int startpoint=0;
//First up; is there a start point on this screen?
for(size_t i=0; i<edentity.size(); i++)
2020-01-01 21:29:24 +01:00
{
//if() on screen
if(edentity[i].t==16 && testeditor==-1)
{
int tx=(edentity[i].x-(edentity[i].x%40))/40;
int ty=(edentity[i].y-(edentity[i].y%30))/30;
if(tx==ed.levx && ty==ed.levy)
{
testeditor=i;
startpoint=1;
}
}
}
if(testeditor==-1)
{
//Ok, settle for a check point
for(size_t i=0; i<edentity.size(); i++)
2020-01-01 21:29:24 +01:00
{
//if() on screen
if(edentity[i].t==10 && testeditor==-1)
{
int tx=(edentity[i].x-(edentity[i].x%40))/40;
int ty=(edentity[i].y-(edentity[i].y%30))/30;
if(tx==ed.levx && ty==ed.levy)
{
testeditor=i;
}
}
}
}
if(testeditor==-1)
{
ed.note="ERROR: No checkpoint to spawn at";
ed.notedelay=45;
}
else
{
ed.currentghosts = 0;
2020-01-01 21:29:24 +01:00
if(startpoint==0)
{
//Checkpoint spawn
int tx=(edentity[testeditor].x-(edentity[testeditor].x%40))/40;
int ty=(edentity[testeditor].y-(edentity[testeditor].y%30))/30;
2020-07-29 19:54:22 +02:00
game.edsavex = (edentity[testeditor].x%40)*8 - 4;
2020-01-01 21:29:24 +01:00
game.edsavey = (edentity[testeditor].y%30)*8;
game.edsaverx = 100+tx;
game.edsavery = 100+ty;
if (edentity[testeditor].p1 == 0) // NOT a bool check!
{
game.edsavegc = 1;
game.edsavey -= 2;
}
else
{
game.edsavegc = 0;
game.edsavey -= 7;
}
2020-01-01 21:29:24 +01:00
game.edsavedir = 0;
}
else
{
//Start point spawn
int tx=(edentity[testeditor].x-(edentity[testeditor].x%40))/40;
int ty=(edentity[testeditor].y-(edentity[testeditor].y%30))/30;
2020-07-29 19:54:22 +02:00
game.edsavex = (edentity[testeditor].x%40)*8 - 4;
2020-01-01 21:29:24 +01:00
game.edsavey = (edentity[testeditor].y%30)*8;
game.edsaverx = 100+tx;
game.edsavery = 100+ty;
game.edsavegc = 0;
game.edsavey++;
2020-01-01 21:29:24 +01:00
game.edsavedir=1-edentity[testeditor].p1;
}
music.haltdasmusik();
graphics.backgrounddrawn=false;
ed.returneditoralpha = 1000; // Let's start it higher than 255 since it gets clamped
ed.oldreturneditoralpha = 1000;
script.startgamemode(21);
2020-01-01 21:29:24 +01:00
}
}
}
ed.hmod = key.keymap[SDLK_h];
ed.vmod = key.keymap[SDLK_v];
ed.bmod = key.keymap[SDLK_b];
ed.cmod = key.keymap[SDLK_c];
ed.xmod = key.keymap[SDLK_x];
ed.zmod = key.keymap[SDLK_z];
2020-01-01 21:29:24 +01:00
if(key.keymap[SDLK_COMMA])
2020-01-01 21:29:24 +01:00
{
ed.drawmode--;
ed.keydelay=6;
2020-01-01 21:29:24 +01:00
}
else if(key.keymap[SDLK_PERIOD])
2020-01-01 21:29:24 +01:00
{
ed.drawmode++;
ed.keydelay=6;
}
2020-01-01 21:29:24 +01:00
if(ed.drawmode<0)
{
ed.drawmode=16;
if(ed.spacemod) ed.spacemenu=0;
}
if(ed.drawmode>16) ed.drawmode=0;
if(ed.drawmode>9)
{
if(ed.spacemod) ed.spacemenu=1;
}
else
{
if(ed.spacemod) ed.spacemenu=0;
}
2020-01-01 21:29:24 +01:00
if(up_pressed)
{
ed.keydelay=6;
graphics.backgrounddrawn=false;
ed.levy--;
ed.updatetiles=true;
ed.changeroom=true;
}
else if(down_pressed)
{
ed.keydelay=6;
graphics.backgrounddrawn=false;
ed.levy++;
ed.updatetiles=true;
ed.changeroom=true;
}
else if(left_pressed)
{
ed.keydelay=6;
graphics.backgrounddrawn=false;
ed.levx--;
ed.updatetiles=true;
ed.changeroom=true;
}
else if(right_pressed)
{
ed.keydelay=6;
graphics.backgrounddrawn=false;
ed.levx++;
ed.updatetiles=true;
ed.changeroom=true;
}
2020-01-01 21:29:24 +01:00
if(ed.levx<0) ed.levx+=ed.mapwidth;
if(ed.levx>= ed.mapwidth) ed.levx-=ed.mapwidth;
if(ed.levy<0) ed.levy+=ed.mapheight;
if(ed.levy>=ed.mapheight) ed.levy-=ed.mapheight;
if(key.keymap[SDLK_SPACE])
{
ed.spacemod = !ed.spacemod;
ed.keydelay=6;
2020-01-01 21:29:24 +01:00
}
}
if(!ed.settingsmod)
{
if(ed.boundarymod>0)
{
if(key.leftbutton)
{
if(ed.lclickdelay==0)
{
if(ed.boundarymod==1)
{
ed.lclickdelay=1;
ed.boundx1=(ed.tilex*8);
ed.boundy1=(ed.tiley*8);
ed.boundarymod=2;
}
else if(ed.boundarymod==2)
{
Fix attempts to place enemy/plat bounds & scripts with bad corner order There is a certain ordering of which corners you click on to place enemy and platform boundaries, and script boxes. You must first click on the top-left corner, then click on the bottom-right corner. The visual box that is drawn after you've first clicked on the top-left corner clearly shows this intended way of doing things. However, it seems like despite the visuals, the game didn't properly prevent you from clicking on the corners in the wrong way. If you placed it from top-right to bottom-left, or bottom-left to top-right, then the game would place the boxes accordingly, and they would have a weird shape where two of its opposite sides would just be missing. But, placing them from bottom-right to top-left is prevented accordingly. The bug comes down to a simple use of "or", instead of the correct "and". This isn't the first time the wrong conjunction was used in a conditional... (8260bb2696f4843b3e6a29b0c4deea7ba6779aea, #136) Since the code block that the if-statement guards is the code that will execute if the corners placed were correct, the if-statement thus should be written in the positive case and use a more restrictive "and", instead of the negative case, which would use a more looser "or". There are less cases that are correct than cases which are incorrect - in this case, there is only 1 correct way of doing things (top-left to bottom-right), compared to 3 incorrect ways of doing things (top-right to bottom-left, bottom-left to top-right, bottom-right to top-left) - so when thinking of positive cases, you should be using "and". Or, you can always just test it. This bug has been in the game since 2.0, so it seems like no one just tested that incorrect input actually didn't work.
2021-01-09 09:27:53 +01:00
if((ed.tilex*8)+8>=ed.boundx1 && (ed.tiley*8)+8>=ed.boundy1)
2020-01-01 21:29:24 +01:00
{
ed.boundx2=(ed.tilex*8)+8;
ed.boundy2=(ed.tiley*8)+8;
}
else
{
ed.boundx2=ed.boundx1+8;
ed.boundy2=ed.boundy1+8;
}
if(ed.boundarytype==0)
{
//Script trigger
ed.lclickdelay=1;
ed.textent=edentity.size();
2020-01-01 21:29:24 +01:00
addedentity((ed.boundx1/8)+(ed.levx*40),(ed.boundy1/8)+ (ed.levy*30),19,
(ed.boundx2-ed.boundx1)/8, (ed.boundy2-ed.boundy1)/8);
ed.getlin(TEXT_SCRIPT, "Enter script name:", &(edentity[ed.textent].scriptname));
2020-01-01 21:29:24 +01:00
ed.lclickdelay=1;
}
else if(ed.boundarytype==1)
{
//Enemy bounds
int tmp=ed.levx+(ed.levy*ed.maxwidth);
ed.level[tmp].enemyx1=ed.boundx1;
ed.level[tmp].enemyy1=ed.boundy1;
ed.level[tmp].enemyx2=ed.boundx2;
ed.level[tmp].enemyy2=ed.boundy2;
}
else if(ed.boundarytype==2)
{
//Platform bounds
int tmp=ed.levx+(ed.levy*ed.maxwidth);
ed.level[tmp].platx1=ed.boundx1;
ed.level[tmp].platy1=ed.boundy1;
ed.level[tmp].platx2=ed.boundx2;
ed.level[tmp].platy2=ed.boundy2;
}
else if(ed.boundarytype==3)
{
//Copy
}
ed.boundarymod=0;
ed.lclickdelay=1;
}
}
}
else
{
ed.lclickdelay=0;
}
if(key.rightbutton)
{
ed.boundarymod=0;
}
}
else if(ed.warpmod)
{
//Placing warp token
if(key.leftbutton)
{
if(ed.lclickdelay==0)
{
if(ed.free(ed.tilex, ed.tiley)==0)
{
edentity[ed.warpent].p1=ed.tilex+(ed.levx*40);
edentity[ed.warpent].p2=ed.tiley+(ed.levy*30);
ed.warpmod=false;
ed.warpent=-1;
ed.lclickdelay=1;
}
}
}
else
{
ed.lclickdelay=0;
}
if(key.rightbutton)
{
removeedentity(ed.warpent);
ed.warpmod=false;
ed.warpent=-1;
}
}
else
{
//Mouse input
if(key.leftbutton)
{
if(ed.lclickdelay==0)
{
//Depending on current mode, place something
if(ed.drawmode==0)
{
//place tiles
//Are we in direct mode?
if(ed.level[ed.levx+(ed.levy*ed.maxwidth)].directmode>=1)
{
if(ed.bmod)
{
for(int i=0; i<30; i++)
{
ed.placetilelocal(ed.tilex, i, ed.dmtile);
}
}
else if(ed.hmod)
{
for(int i=0; i<40; i++)
{
ed.placetilelocal(i, ed.tiley, ed.dmtile);
}
}
else if(ed.vmod)
{
for(int j=-4; j<5; j++)
{
for(int i=-4; i<5; i++)
{
ed.placetilelocal(ed.tilex+i, ed.tiley+j, ed.dmtile);
}
}
}
else if(ed.cmod)
{
for(int j=-3; j<4; j++)
{
for(int i=-3; i<4; i++)
{
ed.placetilelocal(ed.tilex+i, ed.tiley+j, ed.dmtile);
}
}
}
else if(ed.xmod)
2020-01-01 21:29:24 +01:00
{
for(int j=-2; j<3; j++)
{
for(int i=-2; i<3; i++)
{
ed.placetilelocal(ed.tilex+i, ed.tiley+j, ed.dmtile);
}
}
}
else if(ed.zmod)
{
for(int j=-1; j<2; j++)
{
for(int i=-1; i<2; i++)
{
ed.placetilelocal(ed.tilex+i, ed.tiley+j, ed.dmtile);
}
}
}
else
{
ed.placetilelocal(ed.tilex, ed.tiley, ed.dmtile);
}
}
else
{
if(ed.bmod)
{
for(int i=0; i<30; i++)
{
ed.placetilelocal(ed.tilex, i, 80);
}
}
else if(ed.hmod)
{
for(int i=0; i<40; i++)
{
ed.placetilelocal(i, ed.tiley, 80);
}
}
else if(ed.vmod)
{
for(int j=-4; j<5; j++)
{
for(int i=-4; i<5; i++)
{
ed.placetilelocal(ed.tilex+i, ed.tiley+j, 80);
}
}
}
else if(ed.cmod)
{
for(int j=-3; j<4; j++)
{
for(int i=-3; i<4; i++)
{
ed.placetilelocal(ed.tilex+i, ed.tiley+j, 80);
}
}
}
else if(ed.xmod)
2020-01-01 21:29:24 +01:00
{
for(int j=-2; j<3; j++)
{
for(int i=-2; i<3; i++)
{
ed.placetilelocal(ed.tilex+i, ed.tiley+j, 80);
}
}
}
else if(ed.zmod)
{
for(int j=-1; j<2; j++)
{
for(int i=-1; i<2; i++)
{
ed.placetilelocal(ed.tilex+i, ed.tiley+j, 80);
}
}
}
else
{
ed.placetilelocal(ed.tilex, ed.tiley, 80);
}
}
}
else if(ed.drawmode==1)
{
//place background tiles
if(ed.bmod)
{
for(int i=0; i<30; i++)
{
ed.placetilelocal(ed.tilex, i, 2);
}
}
else if(ed.hmod)
{
for(int i=0; i<40; i++)
{
ed.placetilelocal(i, ed.tiley, 2);
}
}
else if(ed.vmod)
{
for(int j=-4; j<5; j++)
{
for(int i=-4; i<5; i++)
{
ed.placetilelocal(ed.tilex+i, ed.tiley+j, 2);
}
}
}
else if(ed.cmod)
{
for(int j=-3; j<4; j++)
{
for(int i=-3; i<4; i++)
{
ed.placetilelocal(ed.tilex+i, ed.tiley+j, 2);
}
}
}
else if(ed.xmod)
2020-01-01 21:29:24 +01:00
{
for(int j=-2; j<3; j++)
{
for(int i=-2; i<3; i++)
{
ed.placetilelocal(ed.tilex+i, ed.tiley+j, 2);
}
}
}
else if(ed.zmod)
{
for(int j=-1; j<2; j++)
{
for(int i=-1; i<2; i++)
{
ed.placetilelocal(ed.tilex+i, ed.tiley+j, 2);
}
}
}
else
{
ed.placetilelocal(ed.tilex, ed.tiley, 2);
}
}
else if(ed.drawmode==2)
{
//place spikes
ed.placetilelocal(ed.tilex, ed.tiley, 8);
}
int tmp=edentat(ed.tilex+ (ed.levx*40),ed.tiley+ (ed.levy*30));
if(tmp==-1)
{
//Room text and script triggers can be placed in walls
if(ed.drawmode==10)
{
ed.lclickdelay=1;
ed.textent=edentity.size();
2020-01-01 21:29:24 +01:00
addedentity(ed.tilex+ (ed.levx*40),ed.tiley+ (ed.levy*30),17);
ed.getlin(TEXT_ROOMTEXT, "Enter roomtext:", &(edentity[ed.textent].scriptname));
2020-01-01 21:29:24 +01:00
}
else if(ed.drawmode==12) //Script Trigger
{
ed.boundarytype=0;
ed.boundx1=ed.tilex*8;
ed.boundy1=ed.tiley*8;
ed.boundarymod=2;
ed.lclickdelay=1;
}
}
if(tmp==-1 && ed.free(ed.tilex,ed.tiley)==0)
{
if(ed.drawmode==3)
{
if(ed.numtrinkets()<100)
2020-01-01 21:29:24 +01:00
{
addedentity(ed.tilex+ (ed.levx*40),ed.tiley+ (ed.levy*30),9);
ed.lclickdelay=1;
}
else
{
ed.note="ERROR: Max number of trinkets is 100";
2020-01-01 21:29:24 +01:00
ed.notedelay=45;
}
}
else if(ed.drawmode==4)
{
addedentity(ed.tilex+ (ed.levx*40),ed.tiley+ (ed.levy*30),10, 1);
ed.lclickdelay=1;
}
else if(ed.drawmode==5)
{
addedentity(ed.tilex+ (ed.levx*40),ed.tiley+ (ed.levy*30),3);
ed.lclickdelay=1;
}
else if(ed.drawmode==6)
{
addedentity(ed.tilex+ (ed.levx*40),ed.tiley+ (ed.levy*30),2,5);
ed.lclickdelay=1;
}
else if(ed.drawmode==7)
{
addedentity(ed.tilex+ (ed.levx*40),ed.tiley+ (ed.levy*30),2,0);
ed.lclickdelay=1;
}
else if(ed.drawmode==8)
{
addedentity(ed.tilex+ (ed.levx*40),ed.tiley+ (ed.levy*30),1,0);
ed.lclickdelay=1;
}
else if(ed.drawmode==9)
{
addedentity(ed.tilex+ (ed.levx*40),ed.tiley+ (ed.levy*30),11,0);
ed.lclickdelay=1;
}
else if(ed.drawmode==11)
{
ed.lclickdelay=1;
ed.textent=edentity.size();
2020-01-01 21:29:24 +01:00
addedentity(ed.tilex+(ed.levx*40),ed.tiley+ (ed.levy*30),18,0);
ed.getlin(TEXT_SCRIPT, "Enter script name", &(edentity[ed.textent].scriptname));
2020-01-01 21:29:24 +01:00
}
else if(ed.drawmode==13)
{
ed.warpmod=true;
ed.warpent=edentity.size();
2020-01-01 21:29:24 +01:00
addedentity(ed.tilex+ (ed.levx*40),ed.tiley+ (ed.levy*30),13);
ed.lclickdelay=1;
}
else if(ed.drawmode==14)
{
//Warp lines
Remove checks to prevent having both warp lines/BGs in editor It's perfectly acceptable to have both warp lines and a warping background in the same room. Many levels do this exact thing, I would say at least 30 or so levels, many of them popular and played by many, and this has never caused any issues at all. All that having both warp lines and warp BG does is make it so the warping of the warping background gets overriden by the warp lines, but make it so the background is still a warp background. So in effect, you can have a warp background without any warping. This is perfectly defined behavior. Except, for whatever reason, it's unintentional, and the editor tries to prevent you from doing it. Key word being "tries". The code to prevent having both warp types is bugged (at least when you change the warp BG. The check when you place warp lines seems to be solid). It compares the p1 and p2 attributes of warp lines to the x-coordinate and y-coordinate of the room, despite p1 and p2 having nothing to do with room coordinates. p1 is the type of the warp line and should be treated as an enum, and p2 is the offset of the warp line from the top/left of the screen. This results in this check sometimes working if you're unlucky, but never actually working properly most of the time. This means people can first place warp lines, and then change the warp background later, to have both warp lines and a warp background. Having these checks just further complicates the code, makes it more error-prone, and just inconveniences people when they want to do something that's perfectly fine to do. So it's best if we just remove these checks.
2020-09-06 05:02:27 +02:00
if(ed.tilex==0)
2020-01-01 21:29:24 +01:00
{
Remove checks to prevent having both warp lines/BGs in editor It's perfectly acceptable to have both warp lines and a warping background in the same room. Many levels do this exact thing, I would say at least 30 or so levels, many of them popular and played by many, and this has never caused any issues at all. All that having both warp lines and warp BG does is make it so the warping of the warping background gets overriden by the warp lines, but make it so the background is still a warp background. So in effect, you can have a warp background without any warping. This is perfectly defined behavior. Except, for whatever reason, it's unintentional, and the editor tries to prevent you from doing it. Key word being "tries". The code to prevent having both warp types is bugged (at least when you change the warp BG. The check when you place warp lines seems to be solid). It compares the p1 and p2 attributes of warp lines to the x-coordinate and y-coordinate of the room, despite p1 and p2 having nothing to do with room coordinates. p1 is the type of the warp line and should be treated as an enum, and p2 is the offset of the warp line from the top/left of the screen. This results in this check sometimes working if you're unlucky, but never actually working properly most of the time. This means people can first place warp lines, and then change the warp background later, to have both warp lines and a warp background. Having these checks just further complicates the code, makes it more error-prone, and just inconveniences people when they want to do something that's perfectly fine to do. So it's best if we just remove these checks.
2020-09-06 05:02:27 +02:00
addedentity(ed.tilex+ (ed.levx*40),ed.tiley+ (ed.levy*30),50,0);
}
else if(ed.tilex==39)
{
addedentity(ed.tilex+ (ed.levx*40),ed.tiley+ (ed.levy*30),50,1);
}
else if(ed.tiley==0)
{
addedentity(ed.tilex+ (ed.levx*40),ed.tiley+ (ed.levy*30),50,2);
}
else if(ed.tiley==29)
{
addedentity(ed.tilex+ (ed.levx*40),ed.tiley+ (ed.levy*30),50,3);
2020-01-01 21:29:24 +01:00
}
else
{
Remove checks to prevent having both warp lines/BGs in editor It's perfectly acceptable to have both warp lines and a warping background in the same room. Many levels do this exact thing, I would say at least 30 or so levels, many of them popular and played by many, and this has never caused any issues at all. All that having both warp lines and warp BG does is make it so the warping of the warping background gets overriden by the warp lines, but make it so the background is still a warp background. So in effect, you can have a warp background without any warping. This is perfectly defined behavior. Except, for whatever reason, it's unintentional, and the editor tries to prevent you from doing it. Key word being "tries". The code to prevent having both warp types is bugged (at least when you change the warp BG. The check when you place warp lines seems to be solid). It compares the p1 and p2 attributes of warp lines to the x-coordinate and y-coordinate of the room, despite p1 and p2 having nothing to do with room coordinates. p1 is the type of the warp line and should be treated as an enum, and p2 is the offset of the warp line from the top/left of the screen. This results in this check sometimes working if you're unlucky, but never actually working properly most of the time. This means people can first place warp lines, and then change the warp background later, to have both warp lines and a warp background. Having these checks just further complicates the code, makes it more error-prone, and just inconveniences people when they want to do something that's perfectly fine to do. So it's best if we just remove these checks.
2020-09-06 05:02:27 +02:00
ed.note="ERROR: Warp lines must be on edges";
2020-01-01 21:29:24 +01:00
ed.notedelay=45;
}
ed.lclickdelay=1;
}
else if(ed.drawmode==15) //Crewmate
{
if(ed.numcrewmates()<100)
2020-01-01 21:29:24 +01:00
{
addedentity(ed.tilex+ (ed.levx*40),ed.tiley+ (ed.levy*30),15,int(fRandom() * 6));
2020-01-01 21:29:24 +01:00
ed.lclickdelay=1;
}
else
{
ed.note="ERROR: Max number of crewmates is 100";
2020-01-01 21:29:24 +01:00
ed.notedelay=45;
}
}
else if(ed.drawmode==16) //Start Point
{
//If there is another start point, destroy it
for(size_t i=0; i<edentity.size(); i++)
2020-01-01 21:29:24 +01:00
{
if(edentity[i].t==16)
{
removeedentity(i);
i--;
}
}
addedentity(ed.tilex+ (ed.levx*40),ed.tiley+ (ed.levy*30),16,0);
ed.lclickdelay=1;
}
}
Fix undefined behavior with left-click logic in editor There's an if-else chain that first deals with figuring out if there's an entity where your left-click happened, and to do this it uses edentat(), which returns a sentinel value of -1 if there is NOT an entity where your cursor is. It's very important to check that the value returned ISN'T -1 before you start indexing the 'edentity' vector, since if you DO index it with that -1, it'll result in Undefined Behavior because you're doing an out-of-bounds array access. Now, here's what the if-else chain looked like before: if(tmp==-1 && ed.free(ed.tilex,ed.tiley)==0) { ... } else if(edentity[tmp].t==1) The bug here is very subtle but it was an easy oversight. Basically, if 'ed.free' ended up not being zero, control flow would jump to the next "else if" over, which then ends up asking for the -1th index of 'edentity', which is Undefined Behavior. This undefined behavior has now resulted in a crash on my system after TerryCavanagh/VVVVVV#172, due it shuffling things around juuuuust enough such that this UB would end up resulting in a segfault instead of chugging along and working fine. For me and my system, this meant that if my first left-click in the editor upon opening the game was me placing down a tile and not placing down an entity, the game would crash. But, it would be fine if I first placed down an entity and then afterwards placed down tiles, because it's UB. And I'm almost certain this was the cause of the very strange bug where you couldn't hold down left-click for the foreground-placing tool (but you COULD for the background-placing tool) that seemed to occur most often on Windows (TerryCavanagh/VVVVVV#25). The solution to this is to stick in another conditional in the tree before any indexing occurs, such that there's no way any other conditionals with the indexing in the conditional tree could end up being hit. In summary, the if-else chain looks like this now: if(tmp==-1 && ed.free(ed.tilex,ed.tiley)==0) { ... } else if(tmp == -1) { //Important! Do nothing, or else Undefined Behavior will happen } else if(edentity[tmp].t==1)
2020-03-02 06:25:29 +01:00
else if(tmp == -1)
{
//Important! Do nothing, or else Undefined Behavior will happen
}
2020-01-01 21:29:24 +01:00
else if(edentity[tmp].t==1)
{
edentity[tmp].p1=(edentity[tmp].p1+1)%4;
ed.lclickdelay=1;
}
else if(edentity[tmp].t==2)
{
if(edentity[tmp].p1>=5)
{
edentity[tmp].p1=(edentity[tmp].p1+1)%9;
if(edentity[tmp].p1<5) edentity[tmp].p1=5;
}
else
{
edentity[tmp].p1=(edentity[tmp].p1+1)%4;
}
ed.lclickdelay=1;
}
else if(edentity[tmp].t==10)
{
edentity[tmp].p1=(edentity[tmp].p1+1)%2;
ed.lclickdelay=1;
}
else if(edentity[tmp].t==11)
{
edentity[tmp].p1=(edentity[tmp].p1+1)%2;
ed.lclickdelay=1;
}
else if(edentity[tmp].t==15)
{
edentity[tmp].p1=(edentity[tmp].p1+1)%6;
ed.lclickdelay=1;
}
else if(edentity[tmp].t==16)
{
edentity[tmp].p1=(edentity[tmp].p1+1)%2;
ed.lclickdelay=1;
}
else if(edentity[tmp].t==17)
{
ed.getlin(TEXT_ROOMTEXT, "Enter roomtext:", &(edentity[tmp].scriptname));
ed.textent=tmp;
2020-01-01 21:29:24 +01:00
ed.lclickdelay=1;
}
else if(edentity[tmp].t==18 || edentity[tmp].t==19)
2020-01-01 21:29:24 +01:00
{
ed.lclickdelay=1;
ed.textent=tmp;
ed.getlin(TEXT_SCRIPT, "Enter script name:", &(edentity[ed.textent].scriptname));
2020-01-01 21:29:24 +01:00
}
}
}
else
{
ed.lclickdelay=0;
}
if(key.rightbutton)
{
//place tiles
if(ed.drawmode < 2) {
if(ed.bmod)
{
for(int i=0; i<30; i++)
{
ed.placetilelocal(ed.tilex, i, 0);
}
}
else if(ed.hmod)
{
for(int i=0; i<40; i++)
{
ed.placetilelocal(i, ed.tiley, 0);
}
}
else if(ed.vmod)
{
for(int j=-4; j<5; j++)
{
for(int i=-4; i<5; i++)
{
ed.placetilelocal(ed.tilex+i, ed.tiley+j, 0);
}
}
}
else if(ed.cmod)
{
for(int j=-3; j<4; j++)
{
for(int i=-3; i<4; i++)
{
ed.placetilelocal(ed.tilex+i, ed.tiley+j, 0);
}
}
}
else if(ed.xmod)
2020-01-01 21:29:24 +01:00
{
for(int j=-2; j<3; j++)
2020-01-01 21:29:24 +01:00
{
for(int i=-2; i<3; i++)
{
ed.placetilelocal(ed.tilex+i, ed.tiley+j, 0);
}
2020-01-01 21:29:24 +01:00
}
}
else if(ed.zmod)
2020-01-01 21:29:24 +01:00
{
for(int j=-1; j<2; j++)
2020-01-01 21:29:24 +01:00
{
for(int i=-1; i<2; i++)
{
ed.placetilelocal(ed.tilex+i, ed.tiley+j, 0);
}
2020-01-01 21:29:24 +01:00
}
}
else
{
ed.placetilelocal(ed.tilex, ed.tiley, 0);
}
2020-01-01 21:29:24 +01:00
}
else
{
ed.placetilelocal(ed.tilex, ed.tiley, 0);
}
for(size_t i=0; i<edentity.size(); i++)
2020-01-01 21:29:24 +01:00
{
if(edentity[i].x==ed.tilex + (ed.levx*40)&& edentity[i].y==ed.tiley+ (ed.levy*30))
{
removeedentity(i);
}
}
}
if(key.middlebutton)
{
ed.dmtile=ed.contents[ed.tilex + (ed.levx*40) + ed.vmult[ed.tiley + (ed.levy*30)]];
}
}
}
}
if(ed.updatetiles && ed.level[ed.levx + (ed.levy*ed.maxwidth)].directmode==0)
{
ed.updatetiles=false;
//Correctly set the tiles in the current room
switch(ed.level[ed.levx + (ed.levy*ed.maxwidth)].tileset)
{
case 0: //The Space Station
for(int j=0; j<30; j++)
{
for(int i=0; i<40; i++)
{
int temp=i+(ed.levx*40) + ed.vmult[j+(ed.levy*30)];
2020-01-01 21:29:24 +01:00
if(ed.contents[temp]>=3 && ed.contents[temp]<80)
{
//Fix spikes
ed.contents[temp]=ed.spikedir(i,j);
}
else if(ed.contents[temp]==2 || ed.contents[temp]>=680)
{
//Fix background
ed.contents[temp]=ed.backedgetile(i,j)+ed.backbase(ed.levx,ed.levy);
}
else if(ed.contents[temp]>0)
{
//Fix tiles
ed.contents[temp]=ed.edgetile(i,j)+ed.base(ed.levx,ed.levy);
}
}
}
break;
case 1: //Outside
for(int j=0; j<30; j++)
{
for(int i=0; i<40; i++)
{
int temp=i+(ed.levx*40) + ed.vmult[j+(ed.levy*30)];
2020-01-01 21:29:24 +01:00
if(ed.contents[temp]>=3 && ed.contents[temp]<80)
{
//Fix spikes
ed.contents[temp]=ed.spikedir(i,j);
}
else if(ed.contents[temp]==2 || ed.contents[temp]>=680)
{
//Fix background
ed.contents[temp]=ed.outsideedgetile(i,j)+ed.backbase(ed.levx,ed.levy);
}
else if(ed.contents[temp]>0)
{
//Fix tiles
ed.contents[temp]=ed.edgetile(i,j)+ed.base(ed.levx,ed.levy);
}
}
}
break;
case 2: //Lab
for(int j=0; j<30; j++)
{
for(int i=0; i<40; i++)
{
int temp=i+(ed.levx*40) + ed.vmult[j+(ed.levy*30)];
2020-01-01 21:29:24 +01:00
if(ed.contents[temp]>=3 && ed.contents[temp]<80)
{
//Fix spikes
ed.contents[temp]=ed.labspikedir(i,j, ed.level[ed.levx + (ed.maxwidth*ed.levy)].tilecol);
}
else if(ed.contents[temp]==2 || ed.contents[temp]>=680)
{
//Fix background
ed.contents[temp]=713;
}
else if(ed.contents[temp]>0)
{
//Fix tiles
ed.contents[temp]=ed.edgetile(i,j)+ed.base(ed.levx,ed.levy);
}
}
}
break;
case 3: //Warp Zone/Intermission
for(int j=0; j<30; j++)
{
for(int i=0; i<40; i++)
{
int temp=i+(ed.levx*40) + ed.vmult[j+(ed.levy*30)];
2020-01-01 21:29:24 +01:00
if(ed.contents[temp]>=3 && ed.contents[temp]<80)
{
//Fix spikes
ed.contents[temp]=ed.spikedir(i,j);
}
else if(ed.contents[temp]==2 || ed.contents[temp]>=680)
{
//Fix background
ed.contents[temp]=713;
2020-01-01 21:29:24 +01:00
}
else if(ed.contents[temp]>0)
{
//Fix tiles
ed.contents[temp]=ed.edgetile(i,j)+ed.base(ed.levx,ed.levy);
}
}
}
break;
case 4: //The ship
for(int j=0; j<30; j++)
{
for(int i=0; i<40; i++)
{
int temp=i+(ed.levx*40) + ed.vmult[j+(ed.levy*30)];
2020-01-01 21:29:24 +01:00
if(ed.contents[temp]>=3 && ed.contents[temp]<80)
{
//Fix spikes
ed.contents[temp]=ed.spikedir(i,j);
}
else if(ed.contents[temp]==2 || ed.contents[temp]>=680)
{
//Fix background
ed.contents[temp]=ed.backedgetile(i,j)+ed.backbase(ed.levx,ed.levy);
}
else if(ed.contents[temp]>0)
{
//Fix tiles
ed.contents[temp]=ed.edgetile(i,j)+ed.base(ed.levx,ed.levy);
}
}
}
break;
case 5: //The Tower
break;
case 6: //Custom Set 1
break;
case 7: //Custom Set 2
break;
case 8: //Custom Set 3
break;
case 9: //Custom Set 4
break;
}
}
}
#endif /* NO_EDITOR */
// Return a graphics-ready color based off of the given tileset and tilecol
// Much kudos to Dav999 for saving me a lot of work, because I stole these colors from const.lua in Ved! -Info Teddy
Uint32 editorclass::getonewaycol(const int rx, const int ry)
{
const int roomnum = rx + ry*maxwidth;
if (roomnum < 0 || roomnum >= 400)
{
return graphics.getRGB(255, 255, 255);
}
const edlevelclass& room = level[roomnum];
switch (room.tileset) {
case 0: // Space Station
switch (room.tilecol) {
case -1:
return graphics.getRGB(109, 109, 109);
case 0:
return graphics.getRGB(131, 141, 235);
case 1:
return graphics.getRGB(227, 140, 227);
case 2:
return graphics.getRGB(242, 126, 151);
case 3:
return graphics.getRGB(229, 235, 133);
case 4:
return graphics.getRGB(148, 238, 130);
case 5:
return graphics.getRGB(140, 165, 227);
case 6:
return graphics.getRGB(227, 140, 148);
case 7:
return graphics.getRGB(140, 173, 228);
case 8:
return graphics.getRGB(142, 235, 137);
case 9:
return graphics.getRGB(137, 235, 206);
case 10:
return graphics.getRGB(235, 139, 223);
case 11:
return graphics.getRGB(238, 130, 138);
case 12:
return graphics.getRGB(137, 235, 178);
case 13:
return graphics.getRGB(125, 205, 247);
case 14:
return graphics.getRGB(190, 137, 235);
case 15:
return graphics.getRGB(235, 137, 206);
case 16:
return graphics.getRGB(229, 247, 127);
case 17:
return graphics.getRGB(127, 200, 247);
case 18:
return graphics.getRGB(197, 137, 235);
case 19:
return graphics.getRGB(235, 131, 175);
case 20:
return graphics.getRGB(242, 210, 123);
case 21:
return graphics.getRGB(131, 235, 158);
case 22:
return graphics.getRGB(242, 126, 151);
case 23:
return graphics.getRGB(219, 243, 123);
case 24:
return graphics.getRGB(131, 234, 145);
case 25:
return graphics.getRGB(131, 199, 234);
case 26:
return graphics.getRGB(141, 131, 234);
case 27:
return graphics.getRGB(226, 140, 144);
case 28:
return graphics.getRGB(129, 236, 144);
case 29:
return graphics.getRGB(235, 231, 131);
case 30:
return graphics.getRGB(153, 235, 131);
case 31:
return graphics.getRGB(207, 131, 235);
}
break;
case 1: // Outside
switch (room.tilecol) {
case 0:
return graphics.getRGB(57, 86, 140);
case 1:
return graphics.getRGB(156, 42, 42);
case 2:
return graphics.getRGB(42, 156, 155);
case 3:
return graphics.getRGB(125, 36, 162);
case 4:
return graphics.getRGB(191, 198, 0);
case 5:
return graphics.getRGB(0, 198, 126);
case 6:
return graphics.getRGB(224, 110, 177);
case 7:
return graphics.getRGB(255, 142, 87);
}
break;
case 2: // Lab
switch (room.tilecol) {
case 0:
return graphics.getRGB(0, 165, 206);
case 1:
return graphics.getRGB(206, 5, 0);
case 2:
return graphics.getRGB(222, 0, 173);
case 3:
return graphics.getRGB(27, 67, 255);
case 4:
return graphics.getRGB(194, 206, 0);
case 5:
return graphics.getRGB(0, 206, 39);
case 6:
return graphics.getRGB(0, 165, 206);
}
break;
case 3: // Warp Zone
switch (room.tilecol) {
case 0:
return graphics.getRGB(113, 178, 197);
case 1:
return graphics.getRGB(197, 113, 119);
case 2:
return graphics.getRGB(196, 113, 197);
case 3:
return graphics.getRGB(149, 113, 197);
case 4:
return graphics.getRGB(197, 182, 113);
case 5:
return graphics.getRGB(141, 197, 113);
case 6:
return graphics.getRGB(109, 109, 109);
}
break;
case 4: // Ship
switch (room.tilecol) {
case 0:
return graphics.getRGB(0, 206, 39);
case 1:
return graphics.getRGB(0, 165, 206);
case 2:
return graphics.getRGB(194, 206, 0);
case 3:
return graphics.getRGB(206, 0, 160);
case 4:
return graphics.getRGB(27, 67, 255);
case 5:
return graphics.getRGB(206, 5, 0);
}
break;
}
// Uh, I guess return solid white
return graphics.getRGB(255, 255, 255);
}
// This version detects the room automatically
Uint32 editorclass::getonewaycol()
{
if (game.gamestate == EDITORMODE)
return getonewaycol(levx, levy);
else if (map.custommode)
return getonewaycol(game.roomx - 100, game.roomy - 100);
// Uh, I guess return solid white
return graphics.getRGB(255, 255, 255);
}
int editorclass::numtrinkets()
{
int temp = 0;
for (size_t i = 0; i < edentity.size(); i++)
{
if (edentity[i].t == 9)
{
temp++;
}
}
return temp;
}
int editorclass::numcrewmates()
{
int temp = 0;
for (size_t i = 0; i < edentity.size(); i++)
{
if (edentity[i].t == 15)
{
temp++;
}
}
return temp;
}
#endif /* NO_CUSTOM_LEVELS */