2020-02-10 03:21:19 +01:00
|
|
|
#if !defined(NO_CUSTOM_LEVELS)
|
2020-02-10 01:53:01 +01:00
|
|
|
|
2020-09-28 04:15:06 +02:00
|
|
|
#define ED_DEFINITION
|
2020-01-01 21:29:24 +01:00
|
|
|
#include "editor.h"
|
|
|
|
|
2021-02-20 05:58:37 +01:00
|
|
|
#include <algorithm>
|
2020-07-19 21:43:29 +02:00
|
|
|
#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"
|
2020-07-19 21:43:29 +02:00
|
|
|
#include "Enums.h"
|
|
|
|
#include "FileSystemUtils.h"
|
|
|
|
#include "Graphics.h"
|
2020-01-01 21:29:24 +01:00
|
|
|
#include "KeyPoll.h"
|
|
|
|
#include "Map.h"
|
2020-07-19 21:43:29 +02:00
|
|
|
#include "Music.h"
|
2020-01-01 21:29:24 +01:00
|
|
|
#include "Script.h"
|
2020-07-19 21:05:41 +02:00
|
|
|
#include "UtilityClass.h"
|
2020-09-25 18:31:03 +02:00
|
|
|
#include "XMLUtils.h"
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-06-18 00:49:40 +02:00
|
|
|
#ifndef __STDC_FORMAT_MACROS
|
|
|
|
#define __STDC_FORMAT_MACROS
|
|
|
|
#endif
|
2020-06-18 01:06:15 +02:00
|
|
|
#ifndef _POSIX_SOURCE
|
|
|
|
#define _POSIX_SOURCE
|
|
|
|
#endif
|
2020-06-16 02:31:54 +02:00
|
|
|
#include <inttypes.h>
|
|
|
|
|
Explicitly declare void for all void parameter functions (#628)
Apparently in C, if you have `void test();`, it's completely okay to do
`test(2);`. The function will take in the argument, but just discard it
and throw it away. It's like a trash can, and a rude one at that. If you
declare it like `void test(void);`, this is prevented.
This is not a problem in C++ - doing `void test();` and `test(2);` is
guaranteed to result in a compile error (this also means that right now,
at least in all `.cpp` files, nobody is ever calling a void parameter
function with arguments and having their arguments be thrown away).
However, we may not be using C++ in the future, so I just want to lay
down the precedent that if a function takes in no arguments, you must
explicitly declare it as such.
I would've added `-Wstrict-prototypes`, but it produces an annoying
warning message saying it doesn't work in C++ mode if you're compiling
in C++ mode. So it can be added later.
2021-02-25 23:23:59 +01:00
|
|
|
edlevelclass::edlevelclass(void)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
Explicitly declare void for all void parameter functions (#628)
Apparently in C, if you have `void test();`, it's completely okay to do
`test(2);`. The function will take in the argument, but just discard it
and throw it away. It's like a trash can, and a rude one at that. If you
declare it like `void test(void);`, this is prevented.
This is not a problem in C++ - doing `void test();` and `test(2);` is
guaranteed to result in a compile error (this also means that right now,
at least in all `.cpp` files, nobody is ever calling a void parameter
function with arguments and having their arguments be thrown away).
However, we may not be using C++ in the future, so I just want to lay
down the precedent that if a function takes in no arguments, you must
explicitly declare it as such.
I would've added `-Wstrict-prototypes`, but it produces an annoying
warning message saying it doesn't work in C++ mode if you're compiling
in C++ mode. So it can be added later.
2021-02-25 23:23:59 +01:00
|
|
|
editorclass::editorclass(void)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//We create a blank map
|
2020-07-03 11:54:31 +02:00
|
|
|
SDL_memset(contents, 0, sizeof(contents));
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-03 11:54:31 +02:00
|
|
|
for (size_t i = 0; i < SDL_arraysize(vmult); i++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-07-03 11:54:31 +02:00
|
|
|
vmult[i] = i * 40 * maxwidth;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
// comparison, not case sensitive.
|
2021-01-10 18:14:37 +01:00
|
|
|
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()) )
|
|
|
|
{
|
2020-07-04 06:48:07 +02:00
|
|
|
if (SDL_tolower(first[i])<SDL_tolower(second[i]))
|
2020-01-01 21:29:24 +01:00
|
|
|
return true;
|
2020-07-04 06:48:07 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
Refactor level dir listing to not use STL data marshalling
Note that level dir listing still uses plenty of STL (including the end
product - the `LevelMetaData` struct - which, for the purposes of 2.3,
is okay enough (2.4 should remove STL usage entirely)); it's just that
the initial act of iterating over the levels directory no longer takes
four or SIX(!!!) heap allocations (not counting reallocations and other
heap allocations this patch does not remove), and no longer does any
data marshalling.
Like text splitting, and binary blob extra indice grabbing, the current
approach that FILESYSTEM_getLevelDirFileNames() uses is a temporary
std::vector of std::strings as a middleman to store all the filenames,
and the game iterates over that std::vector to grab each level metadata.
Except, it's even worse in this case, because PHYSFS_enumerateFiles()
ALREADY does a heap allocation. Oh, and
FILESYSTEM_getLevelDirFileNames() gets called two or three times. Yeah,
let me explain:
1. FILESYSTEM_getLevelDirFileNames() calls PHYSFS_enumerateFiles().
2. PHYSFS_enumerateFiles() allocates an array of pointers to arrays of
chars on the heap. For each filename, it will:
a. Allocate an array of chars for the filename.
b. Reallocate the array of pointers to add the pointer to the above
char array.
(In this step, it also inserts the filename in alphabetically -
without any further allocations, as far as I know - but this is a
COMPLETELY unnecessary step, because we are going to sort the list
of levels by ourselves via the metadata title in the end anyways.)
3. FILESYSTEM_getLevelDirFileNames() iterates over the PhysFS list, and
allocates an std::vector on the heap to shove the list into. Then,
for each filename, it will:
a. Allocate an std::string, initialized to "levels/".
b. Append the filename to the std::string above. This will most
likely require a re-allocation.
c. Duplicate the std::string - which requires allocating more memory
again - to put it into the std::vector.
(Compared to the PhysFS list above, the std::vector does less
reallocations; it however will still end up reallocating a certain
amount of times in the end.)
4. FILESYSTEM_getLevelDirFileNames() will free the PhysFS list.
5. Then to get the std::vector<std::string> back to the caller, we end
up having to reallocate the std::vector again - reallocating every
single std::string inside it, too - to give it back to the caller.
And to top it all off, FILESYSTEM_getLevelDirFileNames() is guaranteed
to either be called two times, or three times. This is because
editorclass::getDirectoryData() will call editorclass::loadZips(), which
will unconditionally call FILESYSTEM_getLevelDirFileNames(), then call
it AGAIN if a zip was found. Then once the function returns,
getDirectoryData() will still unconditionally call
FILESYSTEM_getLevelDirFileNames(). This smells like someone bolting
something on without regard for the whole picture of the system, but
whatever; I can clean up their mess just fine.
So, what do I do about this? Well, just like I did with text splitting
and binary blob extras, make the final for-loop - the one that does the
actual metadata parsing - more immediate.
So how do I do that? Well, PhysFS has a function named
PHYSFS_enumerate(). PHYSFS_enumerateFiles(), in fact, uses this function
internally, and is basically just a wrapper with some allocation and
alphabetization.
PHYSFS_enumerate() takes in a pointer to a function, which it will call
for every single entry that it iterates over. It also lets you pass in
another arbitrary pointer that it leaves alone, which I use to pass
through a function pointer that is the actual callback.
So to clarify, there are two callbacks - one callback is passed through
into another callback that gets passed through to PHYSFS_enumerate().
The callback that gets passed to PHYSFS_enumerate() is always the same,
but the callback that gets passed through the callback can be different
(if you look at the calling code, you can see that one caller passes
through a normal level metadata callback; the other passes through a zip
file callback).
Furthermore, I've also cleaned it up so that if editorclass::loadZips()
finds a zip file, it won't iterate over all the files in the levels
directory a third time. Instead, the level directory only gets iterated
over twice - once to check for zips, and another to load every level
plus all zips; the second time is when all the heap allocations happen.
And with that, level list loading now uses less STL templated stuff and
much less heap allocations.
Also, ed.directoryList basically has no reason to exist other than being
a temporary std::vector, so I've removed it. This further decreases
memory usage, depending on how many levels you have in your levels
folder (I know that I usually have a lot and don't really ever clean it
up, lol).
Lastly, in the callback passed to PhysFS, `builtLocation` is actually no
longer hardcoded to just the `levels` directory, since instead we now
use the `origdir` variable that PhysFS passes us. So that's good, too.
2021-02-19 03:42:13 +01:00
|
|
|
static void levelZipCallback(const char* filename)
|
2020-06-03 20:05:09 +02:00
|
|
|
{
|
2021-02-27 01:03:56 +01:00
|
|
|
if (endsWith(filename, ".zip"))
|
2020-06-03 20:05:09 +02:00
|
|
|
{
|
2021-02-27 01:03:56 +01:00
|
|
|
PHYSFS_File* zip = PHYSFS_openRead(filename);
|
Refactor level dir listing to not use STL data marshalling
Note that level dir listing still uses plenty of STL (including the end
product - the `LevelMetaData` struct - which, for the purposes of 2.3,
is okay enough (2.4 should remove STL usage entirely)); it's just that
the initial act of iterating over the levels directory no longer takes
four or SIX(!!!) heap allocations (not counting reallocations and other
heap allocations this patch does not remove), and no longer does any
data marshalling.
Like text splitting, and binary blob extra indice grabbing, the current
approach that FILESYSTEM_getLevelDirFileNames() uses is a temporary
std::vector of std::strings as a middleman to store all the filenames,
and the game iterates over that std::vector to grab each level metadata.
Except, it's even worse in this case, because PHYSFS_enumerateFiles()
ALREADY does a heap allocation. Oh, and
FILESYSTEM_getLevelDirFileNames() gets called two or three times. Yeah,
let me explain:
1. FILESYSTEM_getLevelDirFileNames() calls PHYSFS_enumerateFiles().
2. PHYSFS_enumerateFiles() allocates an array of pointers to arrays of
chars on the heap. For each filename, it will:
a. Allocate an array of chars for the filename.
b. Reallocate the array of pointers to add the pointer to the above
char array.
(In this step, it also inserts the filename in alphabetically -
without any further allocations, as far as I know - but this is a
COMPLETELY unnecessary step, because we are going to sort the list
of levels by ourselves via the metadata title in the end anyways.)
3. FILESYSTEM_getLevelDirFileNames() iterates over the PhysFS list, and
allocates an std::vector on the heap to shove the list into. Then,
for each filename, it will:
a. Allocate an std::string, initialized to "levels/".
b. Append the filename to the std::string above. This will most
likely require a re-allocation.
c. Duplicate the std::string - which requires allocating more memory
again - to put it into the std::vector.
(Compared to the PhysFS list above, the std::vector does less
reallocations; it however will still end up reallocating a certain
amount of times in the end.)
4. FILESYSTEM_getLevelDirFileNames() will free the PhysFS list.
5. Then to get the std::vector<std::string> back to the caller, we end
up having to reallocate the std::vector again - reallocating every
single std::string inside it, too - to give it back to the caller.
And to top it all off, FILESYSTEM_getLevelDirFileNames() is guaranteed
to either be called two times, or three times. This is because
editorclass::getDirectoryData() will call editorclass::loadZips(), which
will unconditionally call FILESYSTEM_getLevelDirFileNames(), then call
it AGAIN if a zip was found. Then once the function returns,
getDirectoryData() will still unconditionally call
FILESYSTEM_getLevelDirFileNames(). This smells like someone bolting
something on without regard for the whole picture of the system, but
whatever; I can clean up their mess just fine.
So, what do I do about this? Well, just like I did with text splitting
and binary blob extras, make the final for-loop - the one that does the
actual metadata parsing - more immediate.
So how do I do that? Well, PhysFS has a function named
PHYSFS_enumerate(). PHYSFS_enumerateFiles(), in fact, uses this function
internally, and is basically just a wrapper with some allocation and
alphabetization.
PHYSFS_enumerate() takes in a pointer to a function, which it will call
for every single entry that it iterates over. It also lets you pass in
another arbitrary pointer that it leaves alone, which I use to pass
through a function pointer that is the actual callback.
So to clarify, there are two callbacks - one callback is passed through
into another callback that gets passed through to PHYSFS_enumerate().
The callback that gets passed to PHYSFS_enumerate() is always the same,
but the callback that gets passed through the callback can be different
(if you look at the calling code, you can see that one caller passes
through a normal level metadata callback; the other passes through a zip
file callback).
Furthermore, I've also cleaned it up so that if editorclass::loadZips()
finds a zip file, it won't iterate over all the files in the levels
directory a third time. Instead, the level directory only gets iterated
over twice - once to check for zips, and another to load every level
plus all zips; the second time is when all the heap allocations happen.
And with that, level list loading now uses less STL templated stuff and
much less heap allocations.
Also, ed.directoryList basically has no reason to exist other than being
a temporary std::vector, so I've removed it. This further decreases
memory usage, depending on how many levels you have in your levels
folder (I know that I usually have a lot and don't really ever clean it
up, lol).
Lastly, in the callback passed to PhysFS, `builtLocation` is actually no
longer hardcoded to just the `levels` directory, since instead we now
use the `origdir` variable that PhysFS passes us. So that's good, too.
2021-02-19 03:42:13 +01:00
|
|
|
|
2021-02-27 01:03:56 +01:00
|
|
|
if (!PHYSFS_mountHandle(zip, filename, "levels", 1))
|
Refactor level dir listing to not use STL data marshalling
Note that level dir listing still uses plenty of STL (including the end
product - the `LevelMetaData` struct - which, for the purposes of 2.3,
is okay enough (2.4 should remove STL usage entirely)); it's just that
the initial act of iterating over the levels directory no longer takes
four or SIX(!!!) heap allocations (not counting reallocations and other
heap allocations this patch does not remove), and no longer does any
data marshalling.
Like text splitting, and binary blob extra indice grabbing, the current
approach that FILESYSTEM_getLevelDirFileNames() uses is a temporary
std::vector of std::strings as a middleman to store all the filenames,
and the game iterates over that std::vector to grab each level metadata.
Except, it's even worse in this case, because PHYSFS_enumerateFiles()
ALREADY does a heap allocation. Oh, and
FILESYSTEM_getLevelDirFileNames() gets called two or three times. Yeah,
let me explain:
1. FILESYSTEM_getLevelDirFileNames() calls PHYSFS_enumerateFiles().
2. PHYSFS_enumerateFiles() allocates an array of pointers to arrays of
chars on the heap. For each filename, it will:
a. Allocate an array of chars for the filename.
b. Reallocate the array of pointers to add the pointer to the above
char array.
(In this step, it also inserts the filename in alphabetically -
without any further allocations, as far as I know - but this is a
COMPLETELY unnecessary step, because we are going to sort the list
of levels by ourselves via the metadata title in the end anyways.)
3. FILESYSTEM_getLevelDirFileNames() iterates over the PhysFS list, and
allocates an std::vector on the heap to shove the list into. Then,
for each filename, it will:
a. Allocate an std::string, initialized to "levels/".
b. Append the filename to the std::string above. This will most
likely require a re-allocation.
c. Duplicate the std::string - which requires allocating more memory
again - to put it into the std::vector.
(Compared to the PhysFS list above, the std::vector does less
reallocations; it however will still end up reallocating a certain
amount of times in the end.)
4. FILESYSTEM_getLevelDirFileNames() will free the PhysFS list.
5. Then to get the std::vector<std::string> back to the caller, we end
up having to reallocate the std::vector again - reallocating every
single std::string inside it, too - to give it back to the caller.
And to top it all off, FILESYSTEM_getLevelDirFileNames() is guaranteed
to either be called two times, or three times. This is because
editorclass::getDirectoryData() will call editorclass::loadZips(), which
will unconditionally call FILESYSTEM_getLevelDirFileNames(), then call
it AGAIN if a zip was found. Then once the function returns,
getDirectoryData() will still unconditionally call
FILESYSTEM_getLevelDirFileNames(). This smells like someone bolting
something on without regard for the whole picture of the system, but
whatever; I can clean up their mess just fine.
So, what do I do about this? Well, just like I did with text splitting
and binary blob extras, make the final for-loop - the one that does the
actual metadata parsing - more immediate.
So how do I do that? Well, PhysFS has a function named
PHYSFS_enumerate(). PHYSFS_enumerateFiles(), in fact, uses this function
internally, and is basically just a wrapper with some allocation and
alphabetization.
PHYSFS_enumerate() takes in a pointer to a function, which it will call
for every single entry that it iterates over. It also lets you pass in
another arbitrary pointer that it leaves alone, which I use to pass
through a function pointer that is the actual callback.
So to clarify, there are two callbacks - one callback is passed through
into another callback that gets passed through to PHYSFS_enumerate().
The callback that gets passed to PHYSFS_enumerate() is always the same,
but the callback that gets passed through the callback can be different
(if you look at the calling code, you can see that one caller passes
through a normal level metadata callback; the other passes through a zip
file callback).
Furthermore, I've also cleaned it up so that if editorclass::loadZips()
finds a zip file, it won't iterate over all the files in the levels
directory a third time. Instead, the level directory only gets iterated
over twice - once to check for zips, and another to load every level
plus all zips; the second time is when all the heap allocations happen.
And with that, level list loading now uses less STL templated stuff and
much less heap allocations.
Also, ed.directoryList basically has no reason to exist other than being
a temporary std::vector, so I've removed it. This further decreases
memory usage, depending on how many levels you have in your levels
folder (I know that I usually have a lot and don't really ever clean it
up, lol).
Lastly, in the callback passed to PhysFS, `builtLocation` is actually no
longer hardcoded to just the `levels` directory, since instead we now
use the `origdir` variable that PhysFS passes us. So that's good, too.
2021-02-19 03:42:13 +01:00
|
|
|
{
|
|
|
|
printf(
|
|
|
|
"Could not mount %s: %s\n",
|
2021-02-27 01:03:56 +01:00
|
|
|
filename,
|
Refactor level dir listing to not use STL data marshalling
Note that level dir listing still uses plenty of STL (including the end
product - the `LevelMetaData` struct - which, for the purposes of 2.3,
is okay enough (2.4 should remove STL usage entirely)); it's just that
the initial act of iterating over the levels directory no longer takes
four or SIX(!!!) heap allocations (not counting reallocations and other
heap allocations this patch does not remove), and no longer does any
data marshalling.
Like text splitting, and binary blob extra indice grabbing, the current
approach that FILESYSTEM_getLevelDirFileNames() uses is a temporary
std::vector of std::strings as a middleman to store all the filenames,
and the game iterates over that std::vector to grab each level metadata.
Except, it's even worse in this case, because PHYSFS_enumerateFiles()
ALREADY does a heap allocation. Oh, and
FILESYSTEM_getLevelDirFileNames() gets called two or three times. Yeah,
let me explain:
1. FILESYSTEM_getLevelDirFileNames() calls PHYSFS_enumerateFiles().
2. PHYSFS_enumerateFiles() allocates an array of pointers to arrays of
chars on the heap. For each filename, it will:
a. Allocate an array of chars for the filename.
b. Reallocate the array of pointers to add the pointer to the above
char array.
(In this step, it also inserts the filename in alphabetically -
without any further allocations, as far as I know - but this is a
COMPLETELY unnecessary step, because we are going to sort the list
of levels by ourselves via the metadata title in the end anyways.)
3. FILESYSTEM_getLevelDirFileNames() iterates over the PhysFS list, and
allocates an std::vector on the heap to shove the list into. Then,
for each filename, it will:
a. Allocate an std::string, initialized to "levels/".
b. Append the filename to the std::string above. This will most
likely require a re-allocation.
c. Duplicate the std::string - which requires allocating more memory
again - to put it into the std::vector.
(Compared to the PhysFS list above, the std::vector does less
reallocations; it however will still end up reallocating a certain
amount of times in the end.)
4. FILESYSTEM_getLevelDirFileNames() will free the PhysFS list.
5. Then to get the std::vector<std::string> back to the caller, we end
up having to reallocate the std::vector again - reallocating every
single std::string inside it, too - to give it back to the caller.
And to top it all off, FILESYSTEM_getLevelDirFileNames() is guaranteed
to either be called two times, or three times. This is because
editorclass::getDirectoryData() will call editorclass::loadZips(), which
will unconditionally call FILESYSTEM_getLevelDirFileNames(), then call
it AGAIN if a zip was found. Then once the function returns,
getDirectoryData() will still unconditionally call
FILESYSTEM_getLevelDirFileNames(). This smells like someone bolting
something on without regard for the whole picture of the system, but
whatever; I can clean up their mess just fine.
So, what do I do about this? Well, just like I did with text splitting
and binary blob extras, make the final for-loop - the one that does the
actual metadata parsing - more immediate.
So how do I do that? Well, PhysFS has a function named
PHYSFS_enumerate(). PHYSFS_enumerateFiles(), in fact, uses this function
internally, and is basically just a wrapper with some allocation and
alphabetization.
PHYSFS_enumerate() takes in a pointer to a function, which it will call
for every single entry that it iterates over. It also lets you pass in
another arbitrary pointer that it leaves alone, which I use to pass
through a function pointer that is the actual callback.
So to clarify, there are two callbacks - one callback is passed through
into another callback that gets passed through to PHYSFS_enumerate().
The callback that gets passed to PHYSFS_enumerate() is always the same,
but the callback that gets passed through the callback can be different
(if you look at the calling code, you can see that one caller passes
through a normal level metadata callback; the other passes through a zip
file callback).
Furthermore, I've also cleaned it up so that if editorclass::loadZips()
finds a zip file, it won't iterate over all the files in the levels
directory a third time. Instead, the level directory only gets iterated
over twice - once to check for zips, and another to load every level
plus all zips; the second time is when all the heap allocations happen.
And with that, level list loading now uses less STL templated stuff and
much less heap allocations.
Also, ed.directoryList basically has no reason to exist other than being
a temporary std::vector, so I've removed it. This further decreases
memory usage, depending on how many levels you have in your levels
folder (I know that I usually have a lot and don't really ever clean it
up, lol).
Lastly, in the callback passed to PhysFS, `builtLocation` is actually no
longer hardcoded to just the `levels` directory, since instead we now
use the `origdir` variable that PhysFS passes us. So that's good, too.
2021-02-19 03:42:13 +01:00
|
|
|
PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())
|
|
|
|
);
|
2020-06-03 20:05:09 +02:00
|
|
|
}
|
|
|
|
}
|
Refactor level dir listing to not use STL data marshalling
Note that level dir listing still uses plenty of STL (including the end
product - the `LevelMetaData` struct - which, for the purposes of 2.3,
is okay enough (2.4 should remove STL usage entirely)); it's just that
the initial act of iterating over the levels directory no longer takes
four or SIX(!!!) heap allocations (not counting reallocations and other
heap allocations this patch does not remove), and no longer does any
data marshalling.
Like text splitting, and binary blob extra indice grabbing, the current
approach that FILESYSTEM_getLevelDirFileNames() uses is a temporary
std::vector of std::strings as a middleman to store all the filenames,
and the game iterates over that std::vector to grab each level metadata.
Except, it's even worse in this case, because PHYSFS_enumerateFiles()
ALREADY does a heap allocation. Oh, and
FILESYSTEM_getLevelDirFileNames() gets called two or three times. Yeah,
let me explain:
1. FILESYSTEM_getLevelDirFileNames() calls PHYSFS_enumerateFiles().
2. PHYSFS_enumerateFiles() allocates an array of pointers to arrays of
chars on the heap. For each filename, it will:
a. Allocate an array of chars for the filename.
b. Reallocate the array of pointers to add the pointer to the above
char array.
(In this step, it also inserts the filename in alphabetically -
without any further allocations, as far as I know - but this is a
COMPLETELY unnecessary step, because we are going to sort the list
of levels by ourselves via the metadata title in the end anyways.)
3. FILESYSTEM_getLevelDirFileNames() iterates over the PhysFS list, and
allocates an std::vector on the heap to shove the list into. Then,
for each filename, it will:
a. Allocate an std::string, initialized to "levels/".
b. Append the filename to the std::string above. This will most
likely require a re-allocation.
c. Duplicate the std::string - which requires allocating more memory
again - to put it into the std::vector.
(Compared to the PhysFS list above, the std::vector does less
reallocations; it however will still end up reallocating a certain
amount of times in the end.)
4. FILESYSTEM_getLevelDirFileNames() will free the PhysFS list.
5. Then to get the std::vector<std::string> back to the caller, we end
up having to reallocate the std::vector again - reallocating every
single std::string inside it, too - to give it back to the caller.
And to top it all off, FILESYSTEM_getLevelDirFileNames() is guaranteed
to either be called two times, or three times. This is because
editorclass::getDirectoryData() will call editorclass::loadZips(), which
will unconditionally call FILESYSTEM_getLevelDirFileNames(), then call
it AGAIN if a zip was found. Then once the function returns,
getDirectoryData() will still unconditionally call
FILESYSTEM_getLevelDirFileNames(). This smells like someone bolting
something on without regard for the whole picture of the system, but
whatever; I can clean up their mess just fine.
So, what do I do about this? Well, just like I did with text splitting
and binary blob extras, make the final for-loop - the one that does the
actual metadata parsing - more immediate.
So how do I do that? Well, PhysFS has a function named
PHYSFS_enumerate(). PHYSFS_enumerateFiles(), in fact, uses this function
internally, and is basically just a wrapper with some allocation and
alphabetization.
PHYSFS_enumerate() takes in a pointer to a function, which it will call
for every single entry that it iterates over. It also lets you pass in
another arbitrary pointer that it leaves alone, which I use to pass
through a function pointer that is the actual callback.
So to clarify, there are two callbacks - one callback is passed through
into another callback that gets passed through to PHYSFS_enumerate().
The callback that gets passed to PHYSFS_enumerate() is always the same,
but the callback that gets passed through the callback can be different
(if you look at the calling code, you can see that one caller passes
through a normal level metadata callback; the other passes through a zip
file callback).
Furthermore, I've also cleaned it up so that if editorclass::loadZips()
finds a zip file, it won't iterate over all the files in the levels
directory a third time. Instead, the level directory only gets iterated
over twice - once to check for zips, and another to load every level
plus all zips; the second time is when all the heap allocations happen.
And with that, level list loading now uses less STL templated stuff and
much less heap allocations.
Also, ed.directoryList basically has no reason to exist other than being
a temporary std::vector, so I've removed it. This further decreases
memory usage, depending on how many levels you have in your levels
folder (I know that I usually have a lot and don't really ever clean it
up, lol).
Lastly, in the callback passed to PhysFS, `builtLocation` is actually no
longer hardcoded to just the `levels` directory, since instead we now
use the `origdir` variable that PhysFS passes us. So that's good, too.
2021-02-19 03:42:13 +01:00
|
|
|
}
|
2020-06-03 20:05:09 +02:00
|
|
|
|
Explicitly declare void for all void parameter functions (#628)
Apparently in C, if you have `void test();`, it's completely okay to do
`test(2);`. The function will take in the argument, but just discard it
and throw it away. It's like a trash can, and a rude one at that. If you
declare it like `void test(void);`, this is prevented.
This is not a problem in C++ - doing `void test();` and `test(2);` is
guaranteed to result in a compile error (this also means that right now,
at least in all `.cpp` files, nobody is ever calling a void parameter
function with arguments and having their arguments be thrown away).
However, we may not be using C++ in the future, so I just want to lay
down the precedent that if a function takes in no arguments, you must
explicitly declare it as such.
I would've added `-Wstrict-prototypes`, but it produces an annoying
warning message saying it doesn't work in C++ mode if you're compiling
in C++ mode. So it can be added later.
2021-02-25 23:23:59 +01:00
|
|
|
void editorclass::loadZips(void)
|
Refactor level dir listing to not use STL data marshalling
Note that level dir listing still uses plenty of STL (including the end
product - the `LevelMetaData` struct - which, for the purposes of 2.3,
is okay enough (2.4 should remove STL usage entirely)); it's just that
the initial act of iterating over the levels directory no longer takes
four or SIX(!!!) heap allocations (not counting reallocations and other
heap allocations this patch does not remove), and no longer does any
data marshalling.
Like text splitting, and binary blob extra indice grabbing, the current
approach that FILESYSTEM_getLevelDirFileNames() uses is a temporary
std::vector of std::strings as a middleman to store all the filenames,
and the game iterates over that std::vector to grab each level metadata.
Except, it's even worse in this case, because PHYSFS_enumerateFiles()
ALREADY does a heap allocation. Oh, and
FILESYSTEM_getLevelDirFileNames() gets called two or three times. Yeah,
let me explain:
1. FILESYSTEM_getLevelDirFileNames() calls PHYSFS_enumerateFiles().
2. PHYSFS_enumerateFiles() allocates an array of pointers to arrays of
chars on the heap. For each filename, it will:
a. Allocate an array of chars for the filename.
b. Reallocate the array of pointers to add the pointer to the above
char array.
(In this step, it also inserts the filename in alphabetically -
without any further allocations, as far as I know - but this is a
COMPLETELY unnecessary step, because we are going to sort the list
of levels by ourselves via the metadata title in the end anyways.)
3. FILESYSTEM_getLevelDirFileNames() iterates over the PhysFS list, and
allocates an std::vector on the heap to shove the list into. Then,
for each filename, it will:
a. Allocate an std::string, initialized to "levels/".
b. Append the filename to the std::string above. This will most
likely require a re-allocation.
c. Duplicate the std::string - which requires allocating more memory
again - to put it into the std::vector.
(Compared to the PhysFS list above, the std::vector does less
reallocations; it however will still end up reallocating a certain
amount of times in the end.)
4. FILESYSTEM_getLevelDirFileNames() will free the PhysFS list.
5. Then to get the std::vector<std::string> back to the caller, we end
up having to reallocate the std::vector again - reallocating every
single std::string inside it, too - to give it back to the caller.
And to top it all off, FILESYSTEM_getLevelDirFileNames() is guaranteed
to either be called two times, or three times. This is because
editorclass::getDirectoryData() will call editorclass::loadZips(), which
will unconditionally call FILESYSTEM_getLevelDirFileNames(), then call
it AGAIN if a zip was found. Then once the function returns,
getDirectoryData() will still unconditionally call
FILESYSTEM_getLevelDirFileNames(). This smells like someone bolting
something on without regard for the whole picture of the system, but
whatever; I can clean up their mess just fine.
So, what do I do about this? Well, just like I did with text splitting
and binary blob extras, make the final for-loop - the one that does the
actual metadata parsing - more immediate.
So how do I do that? Well, PhysFS has a function named
PHYSFS_enumerate(). PHYSFS_enumerateFiles(), in fact, uses this function
internally, and is basically just a wrapper with some allocation and
alphabetization.
PHYSFS_enumerate() takes in a pointer to a function, which it will call
for every single entry that it iterates over. It also lets you pass in
another arbitrary pointer that it leaves alone, which I use to pass
through a function pointer that is the actual callback.
So to clarify, there are two callbacks - one callback is passed through
into another callback that gets passed through to PHYSFS_enumerate().
The callback that gets passed to PHYSFS_enumerate() is always the same,
but the callback that gets passed through the callback can be different
(if you look at the calling code, you can see that one caller passes
through a normal level metadata callback; the other passes through a zip
file callback).
Furthermore, I've also cleaned it up so that if editorclass::loadZips()
finds a zip file, it won't iterate over all the files in the levels
directory a third time. Instead, the level directory only gets iterated
over twice - once to check for zips, and another to load every level
plus all zips; the second time is when all the heap allocations happen.
And with that, level list loading now uses less STL templated stuff and
much less heap allocations.
Also, ed.directoryList basically has no reason to exist other than being
a temporary std::vector, so I've removed it. This further decreases
memory usage, depending on how many levels you have in your levels
folder (I know that I usually have a lot and don't really ever clean it
up, lol).
Lastly, in the callback passed to PhysFS, `builtLocation` is actually no
longer hardcoded to just the `levels` directory, since instead we now
use the `origdir` variable that PhysFS passes us. So that's good, too.
2021-02-19 03:42:13 +01:00
|
|
|
{
|
|
|
|
FILESYSTEM_enumerateLevelDirFileNames(levelZipCallback);
|
2020-06-03 20:05:09 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 09:08:31 +01:00
|
|
|
static void replace_all(std::string& str, const std::string& from, const std::string& to)
|
2020-04-17 23:19:56 +02:00
|
|
|
{
|
|
|
|
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'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-18 09:08:31 +01:00
|
|
|
static std::string find_tag(const std::string& buf, const std::string& start, const std::string& end)
|
2020-04-17 23:56:38 +02:00
|
|
|
{
|
|
|
|
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, """, "\"");
|
|
|
|
replace_all(value, "&", "&");
|
|
|
|
replace_all(value, "'", "'");
|
|
|
|
replace_all(value, "<", "<");
|
|
|
|
replace_all(value, ">", ">");
|
|
|
|
|
|
|
|
//Encode general XML entities
|
|
|
|
size_t start_pos = 0;
|
|
|
|
while ((start_pos = value.find("&#", start_pos)) != std::string::npos)
|
|
|
|
{
|
2021-02-12 01:07:25 +01:00
|
|
|
if (start_pos + 2 >= value.length())
|
|
|
|
{
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2020-06-16 02:31:54 +02:00
|
|
|
bool hex = value[start_pos + 2] == 'x';
|
2020-04-17 23:56:38 +02:00
|
|
|
size_t end = value.find(';', start_pos);
|
2021-02-12 01:07:25 +01:00
|
|
|
|
|
|
|
if (end == std::string::npos)
|
|
|
|
{
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
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-04-17 23:56:38 +02:00
|
|
|
|
2021-02-12 01:36:22 +01:00
|
|
|
if (!is_positive_num(number.c_str(), hex))
|
2020-04-17 23:56:38 +02:00
|
|
|
{
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2020-06-16 02:31:54 +02:00
|
|
|
uint32_t character = 0;
|
|
|
|
if (hex)
|
|
|
|
{
|
Reduce dependency on libc functions
During 2.3 development, there's been a gradual shift to using SDL stdlib
functions instead of libc functions, but there are still some libc
functions (or the same libc function but from the STL) in the code.
Well, this patch replaces all the rest of them in one fell swoop.
SDL's stdlib can replace most of these, but its SDL_min() and SDL_max()
are inadequate - they aren't really functions, they're more like macros
with a nasty penchant for double-evaluation. So I just made my own
VVV_min() and VVV_max() functions and placed them in Maths.h instead,
then replaced all the previous usages of min(), max(), std::min(),
std::max(), SDL_min(), and SDL_max() with VVV_min() and VVV_max().
Additionally, there's no SDL_isxdigit(), so I just implemented my own
VVV_isxdigit().
SDL has SDL_malloc() and SDL_free(), but they have some refcounting
built in to them, so in order to use them with LodePNG, I have to
replace the malloc() and free() that LodePNG uses. Which isn't too hard,
I did it in a new file called ThirdPartyDeps.c, and LodePNG is now
compiled with the LODEPNG_NO_COMPILE_ALLOCATORS definition.
Lastly, I also refactored the awful strcpy() and strcat() usages in
PLATFORM_migrateSaveData() to use SDL_snprintf() instead. I know save
migration is getting axed in 2.4, but it still bothers me to have
something like that in the codebase otherwise.
Without further ado, here is the full list of functions that the
codebase now uses:
- SDL_strlcpy() instead of strcpy()
- SDL_strlcat() instead of strcat()
- SDL_snprintf() instead of sprintf(), strcpy(), or strcat() (see above)
- VVV_min() instead of min(), std::min(), or SDL_min()
- VVV_max() instead of max(), std::max(), or SDL_max()
- VVV_isxdigit() instead of isxdigit()
- SDL_strcmp() instead of strcmp()
- SDL_strcasecmp() instead of strcasecmp() or Win32 strcmpi()
- SDL_strstr() instead of strstr()
- SDL_strlen() instead of strlen()
- SDL_sscanf() instead of sscanf()
- SDL_getenv() instead of getenv()
- SDL_malloc() instead of malloc() (replacing in LodePNG as well)
- SDL_free() instead of free() (replacing in LodePNG as well)
2021-01-12 01:17:45 +01:00
|
|
|
SDL_sscanf(number.c_str(), "%" SCNx32, &character);
|
2020-06-16 02:31:54 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
Reduce dependency on libc functions
During 2.3 development, there's been a gradual shift to using SDL stdlib
functions instead of libc functions, but there are still some libc
functions (or the same libc function but from the STL) in the code.
Well, this patch replaces all the rest of them in one fell swoop.
SDL's stdlib can replace most of these, but its SDL_min() and SDL_max()
are inadequate - they aren't really functions, they're more like macros
with a nasty penchant for double-evaluation. So I just made my own
VVV_min() and VVV_max() functions and placed them in Maths.h instead,
then replaced all the previous usages of min(), max(), std::min(),
std::max(), SDL_min(), and SDL_max() with VVV_min() and VVV_max().
Additionally, there's no SDL_isxdigit(), so I just implemented my own
VVV_isxdigit().
SDL has SDL_malloc() and SDL_free(), but they have some refcounting
built in to them, so in order to use them with LodePNG, I have to
replace the malloc() and free() that LodePNG uses. Which isn't too hard,
I did it in a new file called ThirdPartyDeps.c, and LodePNG is now
compiled with the LODEPNG_NO_COMPILE_ALLOCATORS definition.
Lastly, I also refactored the awful strcpy() and strcat() usages in
PLATFORM_migrateSaveData() to use SDL_snprintf() instead. I know save
migration is getting axed in 2.4, but it still bothers me to have
something like that in the codebase otherwise.
Without further ado, here is the full list of functions that the
codebase now uses:
- SDL_strlcpy() instead of strcpy()
- SDL_strlcat() instead of strcat()
- SDL_snprintf() instead of sprintf(), strcpy(), or strcat() (see above)
- VVV_min() instead of min(), std::min(), or SDL_min()
- VVV_max() instead of max(), std::max(), or SDL_max()
- VVV_isxdigit() instead of isxdigit()
- SDL_strcmp() instead of strcmp()
- SDL_strcasecmp() instead of strcasecmp() or Win32 strcmpi()
- SDL_strstr() instead of strstr()
- SDL_strlen() instead of strlen()
- SDL_sscanf() instead of sscanf()
- SDL_getenv() instead of getenv()
- SDL_malloc() instead of malloc() (replacing in LodePNG as well)
- SDL_free() instead of free() (replacing in LodePNG as well)
2021-01-12 01:17:45 +01:00
|
|
|
SDL_sscanf(number.c_str(), "%" SCNu32, &character);
|
2020-06-16 02:31:54 +02:00
|
|
|
}
|
|
|
|
uint32_t utf32[] = {character, 0};
|
2020-04-17 23:56:38 +02:00
|
|
|
std::string utf8;
|
|
|
|
utf8::unchecked::utf32to8(utf32, utf32 + 1, std::back_inserter(utf8));
|
|
|
|
value.replace(start_pos, end - start_pos + 1, utf8);
|
|
|
|
}
|
|
|
|
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
2020-04-18 00:06:42 +02:00
|
|
|
#define TAG_FINDER(NAME, TAG) \
|
2021-02-18 09:08:31 +01:00
|
|
|
static std::string NAME(const std::string& buf) \
|
2020-04-18 00:06:42 +02:00
|
|
|
{ \
|
|
|
|
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
|
|
|
|
|
Refactor level dir listing to not use STL data marshalling
Note that level dir listing still uses plenty of STL (including the end
product - the `LevelMetaData` struct - which, for the purposes of 2.3,
is okay enough (2.4 should remove STL usage entirely)); it's just that
the initial act of iterating over the levels directory no longer takes
four or SIX(!!!) heap allocations (not counting reallocations and other
heap allocations this patch does not remove), and no longer does any
data marshalling.
Like text splitting, and binary blob extra indice grabbing, the current
approach that FILESYSTEM_getLevelDirFileNames() uses is a temporary
std::vector of std::strings as a middleman to store all the filenames,
and the game iterates over that std::vector to grab each level metadata.
Except, it's even worse in this case, because PHYSFS_enumerateFiles()
ALREADY does a heap allocation. Oh, and
FILESYSTEM_getLevelDirFileNames() gets called two or three times. Yeah,
let me explain:
1. FILESYSTEM_getLevelDirFileNames() calls PHYSFS_enumerateFiles().
2. PHYSFS_enumerateFiles() allocates an array of pointers to arrays of
chars on the heap. For each filename, it will:
a. Allocate an array of chars for the filename.
b. Reallocate the array of pointers to add the pointer to the above
char array.
(In this step, it also inserts the filename in alphabetically -
without any further allocations, as far as I know - but this is a
COMPLETELY unnecessary step, because we are going to sort the list
of levels by ourselves via the metadata title in the end anyways.)
3. FILESYSTEM_getLevelDirFileNames() iterates over the PhysFS list, and
allocates an std::vector on the heap to shove the list into. Then,
for each filename, it will:
a. Allocate an std::string, initialized to "levels/".
b. Append the filename to the std::string above. This will most
likely require a re-allocation.
c. Duplicate the std::string - which requires allocating more memory
again - to put it into the std::vector.
(Compared to the PhysFS list above, the std::vector does less
reallocations; it however will still end up reallocating a certain
amount of times in the end.)
4. FILESYSTEM_getLevelDirFileNames() will free the PhysFS list.
5. Then to get the std::vector<std::string> back to the caller, we end
up having to reallocate the std::vector again - reallocating every
single std::string inside it, too - to give it back to the caller.
And to top it all off, FILESYSTEM_getLevelDirFileNames() is guaranteed
to either be called two times, or three times. This is because
editorclass::getDirectoryData() will call editorclass::loadZips(), which
will unconditionally call FILESYSTEM_getLevelDirFileNames(), then call
it AGAIN if a zip was found. Then once the function returns,
getDirectoryData() will still unconditionally call
FILESYSTEM_getLevelDirFileNames(). This smells like someone bolting
something on without regard for the whole picture of the system, but
whatever; I can clean up their mess just fine.
So, what do I do about this? Well, just like I did with text splitting
and binary blob extras, make the final for-loop - the one that does the
actual metadata parsing - more immediate.
So how do I do that? Well, PhysFS has a function named
PHYSFS_enumerate(). PHYSFS_enumerateFiles(), in fact, uses this function
internally, and is basically just a wrapper with some allocation and
alphabetization.
PHYSFS_enumerate() takes in a pointer to a function, which it will call
for every single entry that it iterates over. It also lets you pass in
another arbitrary pointer that it leaves alone, which I use to pass
through a function pointer that is the actual callback.
So to clarify, there are two callbacks - one callback is passed through
into another callback that gets passed through to PHYSFS_enumerate().
The callback that gets passed to PHYSFS_enumerate() is always the same,
but the callback that gets passed through the callback can be different
(if you look at the calling code, you can see that one caller passes
through a normal level metadata callback; the other passes through a zip
file callback).
Furthermore, I've also cleaned it up so that if editorclass::loadZips()
finds a zip file, it won't iterate over all the files in the levels
directory a third time. Instead, the level directory only gets iterated
over twice - once to check for zips, and another to load every level
plus all zips; the second time is when all the heap allocations happen.
And with that, level list loading now uses less STL templated stuff and
much less heap allocations.
Also, ed.directoryList basically has no reason to exist other than being
a temporary std::vector, so I've removed it. This further decreases
memory usage, depending on how many levels you have in your levels
folder (I know that I usually have a lot and don't really ever clean it
up, lol).
Lastly, in the callback passed to PhysFS, `builtLocation` is actually no
longer hardcoded to just the `levels` directory, since instead we now
use the `origdir` variable that PhysFS passes us. So that's good, too.
2021-02-19 03:42:13 +01:00
|
|
|
static void levelMetaDataCallback(const char* filename)
|
|
|
|
{
|
|
|
|
extern editorclass ed;
|
|
|
|
LevelMetaData temp;
|
|
|
|
std::string filename_ = filename;
|
|
|
|
|
|
|
|
if (ed.getLevelMetaData(filename_, temp))
|
|
|
|
{
|
|
|
|
ed.ListOfMetaData.push_back(temp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Explicitly declare void for all void parameter functions (#628)
Apparently in C, if you have `void test();`, it's completely okay to do
`test(2);`. The function will take in the argument, but just discard it
and throw it away. It's like a trash can, and a rude one at that. If you
declare it like `void test(void);`, this is prevented.
This is not a problem in C++ - doing `void test();` and `test(2);` is
guaranteed to result in a compile error (this also means that right now,
at least in all `.cpp` files, nobody is ever calling a void parameter
function with arguments and having their arguments be thrown away).
However, we may not be using C++ in the future, so I just want to lay
down the precedent that if a function takes in no arguments, you must
explicitly declare it as such.
I would've added `-Wstrict-prototypes`, but it produces an annoying
warning message saying it doesn't work in C++ mode if you're compiling
in C++ mode. So it can be added later.
2021-02-25 23:23:59 +01:00
|
|
|
void editorclass::getDirectoryData(void)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
|
|
|
|
ListOfMetaData.clear();
|
|
|
|
|
2020-06-03 20:05:09 +02:00
|
|
|
loadZips();
|
|
|
|
|
Refactor level dir listing to not use STL data marshalling
Note that level dir listing still uses plenty of STL (including the end
product - the `LevelMetaData` struct - which, for the purposes of 2.3,
is okay enough (2.4 should remove STL usage entirely)); it's just that
the initial act of iterating over the levels directory no longer takes
four or SIX(!!!) heap allocations (not counting reallocations and other
heap allocations this patch does not remove), and no longer does any
data marshalling.
Like text splitting, and binary blob extra indice grabbing, the current
approach that FILESYSTEM_getLevelDirFileNames() uses is a temporary
std::vector of std::strings as a middleman to store all the filenames,
and the game iterates over that std::vector to grab each level metadata.
Except, it's even worse in this case, because PHYSFS_enumerateFiles()
ALREADY does a heap allocation. Oh, and
FILESYSTEM_getLevelDirFileNames() gets called two or three times. Yeah,
let me explain:
1. FILESYSTEM_getLevelDirFileNames() calls PHYSFS_enumerateFiles().
2. PHYSFS_enumerateFiles() allocates an array of pointers to arrays of
chars on the heap. For each filename, it will:
a. Allocate an array of chars for the filename.
b. Reallocate the array of pointers to add the pointer to the above
char array.
(In this step, it also inserts the filename in alphabetically -
without any further allocations, as far as I know - but this is a
COMPLETELY unnecessary step, because we are going to sort the list
of levels by ourselves via the metadata title in the end anyways.)
3. FILESYSTEM_getLevelDirFileNames() iterates over the PhysFS list, and
allocates an std::vector on the heap to shove the list into. Then,
for each filename, it will:
a. Allocate an std::string, initialized to "levels/".
b. Append the filename to the std::string above. This will most
likely require a re-allocation.
c. Duplicate the std::string - which requires allocating more memory
again - to put it into the std::vector.
(Compared to the PhysFS list above, the std::vector does less
reallocations; it however will still end up reallocating a certain
amount of times in the end.)
4. FILESYSTEM_getLevelDirFileNames() will free the PhysFS list.
5. Then to get the std::vector<std::string> back to the caller, we end
up having to reallocate the std::vector again - reallocating every
single std::string inside it, too - to give it back to the caller.
And to top it all off, FILESYSTEM_getLevelDirFileNames() is guaranteed
to either be called two times, or three times. This is because
editorclass::getDirectoryData() will call editorclass::loadZips(), which
will unconditionally call FILESYSTEM_getLevelDirFileNames(), then call
it AGAIN if a zip was found. Then once the function returns,
getDirectoryData() will still unconditionally call
FILESYSTEM_getLevelDirFileNames(). This smells like someone bolting
something on without regard for the whole picture of the system, but
whatever; I can clean up their mess just fine.
So, what do I do about this? Well, just like I did with text splitting
and binary blob extras, make the final for-loop - the one that does the
actual metadata parsing - more immediate.
So how do I do that? Well, PhysFS has a function named
PHYSFS_enumerate(). PHYSFS_enumerateFiles(), in fact, uses this function
internally, and is basically just a wrapper with some allocation and
alphabetization.
PHYSFS_enumerate() takes in a pointer to a function, which it will call
for every single entry that it iterates over. It also lets you pass in
another arbitrary pointer that it leaves alone, which I use to pass
through a function pointer that is the actual callback.
So to clarify, there are two callbacks - one callback is passed through
into another callback that gets passed through to PHYSFS_enumerate().
The callback that gets passed to PHYSFS_enumerate() is always the same,
but the callback that gets passed through the callback can be different
(if you look at the calling code, you can see that one caller passes
through a normal level metadata callback; the other passes through a zip
file callback).
Furthermore, I've also cleaned it up so that if editorclass::loadZips()
finds a zip file, it won't iterate over all the files in the levels
directory a third time. Instead, the level directory only gets iterated
over twice - once to check for zips, and another to load every level
plus all zips; the second time is when all the heap allocations happen.
And with that, level list loading now uses less STL templated stuff and
much less heap allocations.
Also, ed.directoryList basically has no reason to exist other than being
a temporary std::vector, so I've removed it. This further decreases
memory usage, depending on how many levels you have in your levels
folder (I know that I usually have a lot and don't really ever clean it
up, lol).
Lastly, in the callback passed to PhysFS, `builtLocation` is actually no
longer hardcoded to just the `levels` directory, since instead we now
use the `origdir` variable that PhysFS passes us. So that's good, too.
2021-02-19 03:42:13 +01:00
|
|
|
FILESYSTEM_enumerateLevelDirFileNames(levelMetaDataCallback);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
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]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
bool editorclass::getLevelMetaData(std::string& _path, LevelMetaData& _data )
|
|
|
|
{
|
2020-04-18 00:31:02 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-04-18 00:31:02 +02:00
|
|
|
std::string buf((char*) uMem);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2021-02-16 04:11:51 +01:00
|
|
|
FILESYSTEM_freeMemory(&uMem);
|
|
|
|
|
2020-04-18 00:31:02 +02:00
|
|
|
if (find_metadata(buf) == "")
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-04-18 00:31:02 +02:00
|
|
|
printf("Couldn't load metadata for %s\n", _path.c_str());
|
|
|
|
return false;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-04-18 00:31:02 +02: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);
|
2020-05-08 05:55:26 +02:00
|
|
|
_data.website = find_website(buf);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-04-18 00:31:02 +02:00
|
|
|
_data.filename = _path;
|
|
|
|
return true;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
Explicitly declare void for all void parameter functions (#628)
Apparently in C, if you have `void test();`, it's completely okay to do
`test(2);`. The function will take in the argument, but just discard it
and throw it away. It's like a trash can, and a rude one at that. If you
declare it like `void test(void);`, this is prevented.
This is not a problem in C++ - doing `void test();` and `test(2);` is
guaranteed to result in a compile error (this also means that right now,
at least in all `.cpp` files, nobody is ever calling a void parameter
function with arguments and having their arguments be thrown away).
However, we may not be using C++ in the future, so I just want to lay
down the precedent that if a function takes in no arguments, you must
explicitly declare it as such.
I would've added `-Wstrict-prototypes`, but it produces an annoying
warning message saying it doesn't work in C++ mode if you're compiling
in C++ mode. So it can be added later.
2021-02-25 23:23:59 +01:00
|
|
|
void editorclass::reset(void)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
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;
|
2020-06-17 23:49:57 +02:00
|
|
|
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;
|
2020-05-02 19:42:39 +02:00
|
|
|
oldnotedelay=0;
|
2020-01-01 21:29:24 +01:00
|
|
|
deletekeyheld=false;
|
2020-07-01 08:17:26 +02:00
|
|
|
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;
|
|
|
|
|
2020-01-27 11:15:25 +01:00
|
|
|
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;
|
|
|
|
|
2020-03-01 21:24:43 +01:00
|
|
|
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;
|
2020-01-23 10:26:29 +01:00
|
|
|
level[i+(j*maxwidth)].enemytype=0;
|
2020-01-20 06:46:52 +01:00
|
|
|
level[i+(j*maxwidth)].directmode=0;
|
2020-01-31 04:06:16 +01:00
|
|
|
kludgewarpdir[i+(j*maxwidth)]=0;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-13 01:35:22 +01:00
|
|
|
SDL_zeroa(contents);
|
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;
|
2021-02-13 01:35:22 +01:00
|
|
|
script.clearcustom();
|
2020-02-11 06:34:01 +01:00
|
|
|
|
|
|
|
returneditoralpha = 0;
|
2020-05-02 19:49:41 +02:00
|
|
|
oldreturneditoralpha = 0;
|
Add a player trail to the editor (ghosts)
A few months ago, I added ghosts to the VVVVVV: Community Edition editor. I was told recently I should think
about upstreaming it, and with Terry saying go ahead I finally ported them into VVVVVV. There's one slight
difference however--you can choose whether you have them or not in the editor's settings menu. They're off by
default, and this is saved to the save file.
Anyway, when you're playtesting, the game saves the players position, color, room coordinates and sprite every 3
frames. The max is 100, where if it tries to add more, the oldest one gets removed.
When you exit playtesting, the saved positions appear one at a time, and you can use the Z key to speed it up.
[Here's a video of them in action.](https://o.lol-sa.me/4H21zCv.mp4)
2020-06-13 00:04:35 +02:00
|
|
|
|
|
|
|
ghosts.clear();
|
2020-06-13 00:27:21 +02:00
|
|
|
currentghosts = 0;
|
2020-06-30 23:08:14 +02:00
|
|
|
|
|
|
|
onewaycol_override = false;
|
2020-11-03 18:55:26 +01:00
|
|
|
|
|
|
|
loaded_filepath = "";
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
Explicitly declare void for all void parameter functions (#628)
Apparently in C, if you have `void test();`, it's completely okay to do
`test(2);`. The function will take in the argument, but just discard it
and throw it away. It's like a trash can, and a rude one at that. If you
declare it like `void test(void);`, this is prevented.
This is not a problem in C++ - doing `void test();` and `test(2);` is
guaranteed to result in a compile error (this also means that right now,
at least in all `.cpp` files, nobody is ever calling a void parameter
function with arguments and having their arguments be thrown away).
However, we may not be using C++ in the future, so I just want to lay
down the precedent that if a function takes in no arguments, you must
explicitly declare it as such.
I would've added `-Wstrict-prototypes`, but it produces an annoying
warning message saying it doesn't work in C++ mode if you're compiling
in C++ mode. So it can be added later.
2021-02-25 23:23:59 +01:00
|
|
|
void editorclass::gethooks(void)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//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();
|
Refactor custom scripts to not be stored in one giant vector of lines
This commit refactors custom level scripts to no longer be stored in one
giant vector containing not only every single script name, but every
single script's contents as well. More specifically,
scriptclass::customscript has been converted to an std::vector<Script>
scriptclass::customscripts (note the extra S), and a Script is just a
struct with an std::string name and std::vector<std::string> contents.
This is an improvement in both performance and maintainability. The game
no longer has to look through script contents in case they're actually
script names, and then manually extract the script contents from there.
Instead, all it has to do is look for script names only. And the
contents are provided for free. This results in a performance gain.
Also, the old system resulted in lots of boilerplate everywhere anytime
scripts had to be handled or parsed. Now, the boilerplate is only done
when saving or loading a custom level. This makes code quality much,
much better.
To be sure I didn't actually change anything, I tested by first saving
Dimension Open in current 2.3 (because current 2.3 gets rid of the
awful edentity whitespace), and then resaved it on this patch. There is
absolutely no difference between the current-2.3-resave and
this-patch-resave.
2020-06-12 02:31:57 +02:00
|
|
|
for (size_t i = 0; i < script.customscripts.size(); i++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Refactor custom scripts to not be stored in one giant vector of lines
This commit refactors custom level scripts to no longer be stored in one
giant vector containing not only every single script name, but every
single script's contents as well. More specifically,
scriptclass::customscript has been converted to an std::vector<Script>
scriptclass::customscripts (note the extra S), and a Script is just a
struct with an std::string name and std::vector<std::string> contents.
This is an improvement in both performance and maintainability. The game
no longer has to look through script contents in case they're actually
script names, and then manually extract the script contents from there.
Instead, all it has to do is look for script names only. And the
contents are provided for free. This results in a performance gain.
Also, the old system resulted in lots of boilerplate everywhere anytime
scripts had to be handled or parsed. Now, the boilerplate is only done
when saving or loading a custom level. This makes code quality much,
much better.
To be sure I didn't actually change anything, I tested by first saving
Dimension Open in current 2.3 (because current 2.3 gets rid of the
awful edentity whitespace), and then resaved it on this patch. There is
absolutely no difference between the current-2.3-resave and
this-patch-resave.
2020-06-12 02:31:57 +02: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();
|
|
|
|
|
Refactor custom scripts to not be stored in one giant vector of lines
This commit refactors custom level scripts to no longer be stored in one
giant vector containing not only every single script name, but every
single script's contents as well. More specifically,
scriptclass::customscript has been converted to an std::vector<Script>
scriptclass::customscripts (note the extra S), and a Script is just a
struct with an std::string name and std::vector<std::string> contents.
This is an improvement in both performance and maintainability. The game
no longer has to look through script contents in case they're actually
script names, and then manually extract the script contents from there.
Instead, all it has to do is look for script names only. And the
contents are provided for free. This results in a performance gain.
Also, the old system resulted in lots of boilerplate everywhere anytime
scripts had to be handled or parsed. Now, the boilerplate is only done
when saving or loading a custom level. This makes code quality much,
much better.
To be sure I didn't actually change anything, I tested by first saving
Dimension Open in current 2.3 (because current 2.3 gets rid of the
awful edentity whitespace), and then resaved it on this patch. There is
absolutely no difference between the current-2.3-resave and
this-patch-resave.
2020-06-12 02:31:57 +02:00
|
|
|
for(size_t i = 0; i < script.customscripts.size(); i++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Refactor custom scripts to not be stored in one giant vector of lines
This commit refactors custom level scripts to no longer be stored in one
giant vector containing not only every single script name, but every
single script's contents as well. More specifically,
scriptclass::customscript has been converted to an std::vector<Script>
scriptclass::customscripts (note the extra S), and a Script is just a
struct with an std::string name and std::vector<std::string> contents.
This is an improvement in both performance and maintainability. The game
no longer has to look through script contents in case they're actually
script names, and then manually extract the script contents from there.
Instead, all it has to do is look for script names only. And the
contents are provided for free. This results in a performance gain.
Also, the old system resulted in lots of boilerplate everywhere anytime
scripts had to be handled or parsed. Now, the boilerplate is only done
when saving or loading a custom level. This makes code quality much,
much better.
To be sure I didn't actually change anything, I tested by first saving
Dimension Open in current 2.3 (because current 2.3 gets rid of the
awful edentity whitespace), and then resaved it on this patch. There is
absolutely no difference between the current-2.3-resave and
this-patch-resave.
2020-06-12 02:31:57 +02:00
|
|
|
Script& script_ = script.customscripts[i];
|
|
|
|
|
|
|
|
if(script_.name == t)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Refactor custom scripts to not be stored in one giant vector of lines
This commit refactors custom level scripts to no longer be stored in one
giant vector containing not only every single script name, but every
single script's contents as well. More specifically,
scriptclass::customscript has been converted to an std::vector<Script>
scriptclass::customscripts (note the extra S), and a Script is just a
struct with an std::string name and std::vector<std::string> contents.
This is an improvement in both performance and maintainability. The game
no longer has to look through script contents in case they're actually
script names, and then manually extract the script contents from there.
Instead, all it has to do is look for script names only. And the
contents are provided for free. This results in a performance gain.
Also, the old system resulted in lots of boilerplate everywhere anytime
scripts had to be handled or parsed. Now, the boilerplate is only done
when saving or loading a custom level. This makes code quality much,
much better.
To be sure I didn't actually change anything, I tested by first saving
Dimension Open in current 2.3 (because current 2.3 gets rid of the
awful edentity whitespace), and then resaved it on this patch. There is
absolutely no difference between the current-2.3-resave and
this-patch-resave.
2020-06-12 02:31:57 +02:00
|
|
|
sb = script_.contents;
|
|
|
|
break;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
Refactor custom scripts to not be stored in one giant vector of lines
This commit refactors custom level scripts to no longer be stored in one
giant vector containing not only every single script name, but every
single script's contents as well. More specifically,
scriptclass::customscript has been converted to an std::vector<Script>
scriptclass::customscripts (note the extra S), and a Script is just a
struct with an std::string name and std::vector<std::string> contents.
This is an improvement in both performance and maintainability. The game
no longer has to look through script contents in case they're actually
script names, and then manually extract the script contents from there.
Instead, all it has to do is look for script names only. And the
contents are provided for free. This results in a performance gain.
Also, the old system resulted in lots of boilerplate everywhere anytime
scripts had to be handled or parsed. Now, the boilerplate is only done
when saving or loading a custom level. This makes code quality much,
much better.
To be sure I didn't actually change anything, I tested by first saving
Dimension Open in current 2.3 (because current 2.3 gets rid of the
awful edentity whitespace), and then resaved it on this patch. There is
absolutely no difference between the current-2.3-resave and
this-patch-resave.
2020-06-12 02:31:57 +02: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);
|
Refactor custom scripts to not be stored in one giant vector of lines
This commit refactors custom level scripts to no longer be stored in one
giant vector containing not only every single script name, but every
single script's contents as well. More specifically,
scriptclass::customscript has been converted to an std::vector<Script>
scriptclass::customscripts (note the extra S), and a Script is just a
struct with an std::string name and std::vector<std::string> contents.
This is an improvement in both performance and maintainability. The game
no longer has to look through script contents in case they're actually
script names, and then manually extract the script contents from there.
Instead, all it has to do is look for script names only. And the
contents are provided for free. This results in a performance gain.
Also, the old system resulted in lots of boilerplate everywhere anytime
scripts had to be handled or parsed. Now, the boilerplate is only done
when saving or loading a custom level. This makes code quality much,
much better.
To be sure I didn't actually change anything, I tested by first saving
Dimension Open in current 2.3 (because current 2.3 gets rid of the
awful edentity whitespace), and then resaved it on this patch. There is
absolutely no difference between the current-2.3-resave and
this-patch-resave.
2020-06-12 02:31:57 +02:00
|
|
|
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)
|
Refactor custom scripts to not be stored in one giant vector of lines
This commit refactors custom level scripts to no longer be stored in one
giant vector containing not only every single script name, but every
single script's contents as well. More specifically,
scriptclass::customscript has been converted to an std::vector<Script>
scriptclass::customscripts (note the extra S), and a Script is just a
struct with an std::string name and std::vector<std::string> contents.
This is an improvement in both performance and maintainability. The game
no longer has to look through script contents in case they're actually
script names, and then manually extract the script contents from there.
Instead, all it has to do is look for script names only. And the
contents are provided for free. This results in a performance gain.
Also, the old system resulted in lots of boilerplate everywhere anytime
scripts had to be handled or parsed. Now, the boilerplate is only done
when saving or loading a custom level. This makes code quality much,
much better.
To be sure I didn't actually change anything, I tested by first saving
Dimension Open in current 2.3 (because current 2.3 gets rid of the
awful edentity whitespace), and then resaved it on this patch. There is
absolutely no difference between the current-2.3-resave and
this-patch-resave.
2020-06-12 02:31:57 +02:00
|
|
|
for (size_t i = 0; i < script.customscripts.size(); i++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Refactor custom scripts to not be stored in one giant vector of lines
This commit refactors custom level scripts to no longer be stored in one
giant vector containing not only every single script name, but every
single script's contents as well. More specifically,
scriptclass::customscript has been converted to an std::vector<Script>
scriptclass::customscripts (note the extra S), and a Script is just a
struct with an std::string name and std::vector<std::string> contents.
This is an improvement in both performance and maintainability. The game
no longer has to look through script contents in case they're actually
script names, and then manually extract the script contents from there.
Instead, all it has to do is look for script names only. And the
contents are provided for free. This results in a performance gain.
Also, the old system resulted in lots of boilerplate everywhere anytime
scripts had to be handled or parsed. Now, the boilerplate is only done
when saving or loading a custom level. This makes code quality much,
much better.
To be sure I didn't actually change anything, I tested by first saving
Dimension Open in current 2.3 (because current 2.3 gets rid of the
awful edentity whitespace), and then resaved it on this patch. There is
absolutely no difference between the current-2.3-resave and
this-patch-resave.
2020-06-12 02:31:57 +02:00
|
|
|
Script& script_ = script.customscripts[i];
|
2020-01-01 21:29:24 +01:00
|
|
|
|
Refactor custom scripts to not be stored in one giant vector of lines
This commit refactors custom level scripts to no longer be stored in one
giant vector containing not only every single script name, but every
single script's contents as well. More specifically,
scriptclass::customscript has been converted to an std::vector<Script>
scriptclass::customscripts (note the extra S), and a Script is just a
struct with an std::string name and std::vector<std::string> contents.
This is an improvement in both performance and maintainability. The game
no longer has to look through script contents in case they're actually
script names, and then manually extract the script contents from there.
Instead, all it has to do is look for script names only. And the
contents are provided for free. This results in a performance gain.
Also, the old system resulted in lots of boilerplate everywhere anytime
scripts had to be handled or parsed. Now, the boilerplate is only done
when saving or loading a custom level. This makes code quality much,
much better.
To be sure I didn't actually change anything, I tested by first saving
Dimension Open in current 2.3 (because current 2.3 gets rid of the
awful edentity whitespace), and then resaved it on this patch. There is
absolutely no difference between the current-2.3-resave and
this-patch-resave.
2020-06-12 02:31:57 +02:00
|
|
|
if (script_.name == t)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Refactor custom scripts to not be stored in one giant vector of lines
This commit refactors custom level scripts to no longer be stored in one
giant vector containing not only every single script name, but every
single script's contents as well. More specifically,
scriptclass::customscript has been converted to an std::vector<Script>
scriptclass::customscripts (note the extra S), and a Script is just a
struct with an std::string name and std::vector<std::string> contents.
This is an improvement in both performance and maintainability. The game
no longer has to look through script contents in case they're actually
script names, and then manually extract the script contents from there.
Instead, all it has to do is look for script names only. And the
contents are provided for free. This results in a performance gain.
Also, the old system resulted in lots of boilerplate everywhere anytime
scripts had to be handled or parsed. Now, the boilerplate is only done
when saving or loading a custom level. This makes code quality much,
much better.
To be sure I didn't actually change anything, I tested by first saving
Dimension Open in current 2.3 (because current 2.3 gets rid of the
awful edentity whitespace), and then resaved it on this patch. There is
absolutely no difference between the current-2.3-resave and
this-patch-resave.
2020-06-12 02:31:57 +02: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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
Explicitly declare void for all void parameter functions (#628)
Apparently in C, if you have `void test();`, it's completely okay to do
`test(2);`. The function will take in the argument, but just discard it
and throw it away. It's like a trash can, and a rude one at that. If you
declare it like `void test(void);`, this is prevented.
This is not a problem in C++ - doing `void test();` and `test(2);` is
guaranteed to result in a compile error (this also means that right now,
at least in all `.cpp` files, nobody is ever calling a void parameter
function with arguments and having their arguments be thrown away).
However, we may not be using C++ in the future, so I just want to lay
down the precedent that if a function takes in no arguments, you must
explicitly declare it as such.
I would've added `-Wstrict-prototypes`, but it produces an annoying
warning message saying it doesn't work in C++ mode if you're compiling
in C++ mode. So it can be added later.
2021-02-25 23:23:59 +01:00
|
|
|
void editorclass::clearscriptbuffer(void)
|
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
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2020-07-01 08:17:26 +02:00
|
|
|
void editorclass::getlin(const enum textmode mode, const std::string& prompt, std::string* ptr)
|
|
|
|
{
|
2020-09-28 04:15:06 +02:00
|
|
|
textmod = mode;
|
|
|
|
textptr = ptr;
|
|
|
|
textdesc = prompt;
|
2020-07-01 08:17:26 +02:00
|
|
|
key.enabletextentry();
|
|
|
|
if (ptr)
|
|
|
|
{
|
|
|
|
key.keybuffer = *ptr;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
key.keybuffer = "";
|
2020-09-28 04:15:06 +02:00
|
|
|
textptr = &(key.keybuffer);
|
2020-07-01 08:17:26 +02:00
|
|
|
}
|
|
|
|
|
2020-09-28 04:15:06 +02:00
|
|
|
oldenttext = key.keybuffer;
|
2020-07-01 08:17:26 +02:00
|
|
|
}
|
|
|
|
|
2020-07-19 06:06:35 +02:00
|
|
|
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;
|
|
|
|
|
2020-07-19 06:06:35 +02:00
|
|
|
static short result[1200];
|
2020-05-22 04:23:21 +02:00
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
for (int j = 0; j < 30; j++)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < 40; i++)
|
|
|
|
{
|
2020-07-03 11:06:46 +02:00
|
|
|
result[i + j*40] = contents[i+(rxi*40)+vmult[j+(ryi*30)]];
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
2020-05-22 04:23:21 +02: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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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
|
2020-04-03 03:50:37 +02:00
|
|
|
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
|
2020-04-03 03:50:37 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
Explicitly declare void for all void parameter functions (#628)
Apparently in C, if you have `void test();`, it's completely okay to do
`test(2);`. The function will take in the argument, but just discard it
and throw it away. It's like a trash can, and a rude one at that. If you
declare it like `void test(void);`, this is prevented.
This is not a problem in C++ - doing `void test();` and `test(2);` is
guaranteed to result in a compile error (this also means that right now,
at least in all `.cpp` files, nobody is ever calling a void parameter
function with arguments and having their arguments be thrown away).
However, we may not be using C++ in the future, so I just want to lay
down the precedent that if a function takes in no arguments, you must
explicitly declare it as such.
I would've added `-Wstrict-prototypes`, but it produces an annoying
warning message saying it doesn't work in C++ mode if you're compiling
in C++ mode. So it can be added later.
2021-02-25 23:23:59 +01:00
|
|
|
void editorclass::findstartpoint(void)
|
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?
|
2020-03-01 21:24:43 +01:00
|
|
|
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--;
|
2020-01-22 06:43:01 +01:00
|
|
|
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;
|
2020-10-06 08:13:07 +02:00
|
|
|
game.edsavey++;
|
2020-01-01 21:29:24 +01:00
|
|
|
game.edsavedir=1-edentity[testeditor].p1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int editorclass::findtrinket(int t)
|
|
|
|
{
|
|
|
|
int ttrinket=0;
|
2020-03-01 21:24:43 +01:00
|
|
|
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;
|
2020-03-01 21:24:43 +01:00
|
|
|
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;
|
2020-03-01 21:24:43 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-07-01 06:39:10 +02:00
|
|
|
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;
|
2020-09-10 07:12:10 +02:00
|
|
|
if (!INBOUNDS_ARR(roomnum, level))
|
2020-07-01 06:39:10 +02:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
edlevelclass& room = level[roomnum];
|
|
|
|
|
|
|
|
int tiles = room.tileset;
|
|
|
|
|
|
|
|
if (reversed)
|
|
|
|
{
|
|
|
|
tiles--;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
tiles++;
|
|
|
|
}
|
|
|
|
|
Fix Shift+F1 from Space Station tileset not switching to Ship
So... it looks like being able to switch through tilesets backwards has
been in 2.3 for a while, guess no one just uses 2.3 or the level editor
that much. It seems like it's always been broken, too.
If you were on the Space Station tileset (tileset 0), pressing Shift+F1
would keep you on the Space Station tileset instead of switching to the
Ship (tileset 4).
It looks like the problem here was mixing size_t and int together - so
the modulus operation promoted the left-hand side to size_t, which is
unsigned, so the -1 turned into SIZE_MAX, which is 18446744073709551615
on my system. You'll note that that ends in a 5, so the number is
divisible by 5, meaning taking it modulo 5 leads to 0. So the tileset
would be kept at 0.
At least unsigned integer underflow/overflow is properly defined, so
there's no UB here. Just careless type mixing going on.
The solution is to make the modulus an int instead of a size_t. This
introduces an implicit conversion, but I don't care because my compiler
doesn't warn about it, and implicit conversion warnings ought to be
disabled on MSVC anyway.
2021-01-09 00:02:17 +01:00
|
|
|
const int modulus = SDL_arraysize(tilesets);
|
2020-07-01 06:39:10 +02:00
|
|
|
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;
|
2020-09-10 07:12:10 +02:00
|
|
|
if (!INBOUNDS_ARR(roomnum, level))
|
2020-07-01 06:39:10 +02:00
|
|
|
{
|
|
|
|
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;
|
2020-09-10 07:12:10 +02:00
|
|
|
if (!INBOUNDS_ARR(roomnum, level))
|
2020-07-01 06:39:10 +02:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
edlevelclass& room = level[rx + ry*maxwidth];
|
|
|
|
|
|
|
|
const int tileset = room.tileset;
|
|
|
|
int tilecol = room.tilecol;
|
|
|
|
|
2020-07-01 06:47:03 +02:00
|
|
|
int mincol = -1;
|
2020-07-01 06:39:10 +02:00
|
|
|
int maxcol = 5;
|
|
|
|
|
2020-07-01 06:47:03 +02:00
|
|
|
// Only Space Station allows tileset -1
|
|
|
|
if (tileset != 0)
|
|
|
|
{
|
|
|
|
mincol = 0;
|
|
|
|
}
|
|
|
|
|
2020-07-01 06:39:10 +02:00
|
|
|
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;
|
2020-09-10 07:12:10 +02:00
|
|
|
if (!INBOUNDS_ARR(roomnum, level))
|
2020-07-01 06:39:10 +02:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
edlevelclass& room = level[roomnum];
|
|
|
|
|
2020-07-17 22:25:23 +02:00
|
|
|
int enemy = room.enemytype;
|
|
|
|
|
2020-07-01 06:39:10 +02:00
|
|
|
if (reversed)
|
|
|
|
{
|
2020-07-17 22:25:23 +02:00
|
|
|
enemy--;
|
2020-07-01 06:39:10 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-07-17 22:25:23 +02:00
|
|
|
enemy++;
|
2020-07-01 06:39:10 +02:00
|
|
|
}
|
|
|
|
|
2020-07-17 22:25:23 +02:00
|
|
|
const int modulus = 10;
|
|
|
|
enemy = (enemy % modulus + modulus) % modulus;
|
|
|
|
room.enemytype = enemy;
|
|
|
|
|
2020-07-01 06:39:10 +02:00
|
|
|
note = "Enemy Type Changed";
|
|
|
|
notedelay = 45;
|
|
|
|
}
|
|
|
|
|
2020-06-02 12:59:54 +02:00
|
|
|
bool editorclass::load(std::string& _path)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
reset();
|
|
|
|
|
|
|
|
static const char *levelDir = "levels/";
|
Reduce dependency on libc functions
During 2.3 development, there's been a gradual shift to using SDL stdlib
functions instead of libc functions, but there are still some libc
functions (or the same libc function but from the STL) in the code.
Well, this patch replaces all the rest of them in one fell swoop.
SDL's stdlib can replace most of these, but its SDL_min() and SDL_max()
are inadequate - they aren't really functions, they're more like macros
with a nasty penchant for double-evaluation. So I just made my own
VVV_min() and VVV_max() functions and placed them in Maths.h instead,
then replaced all the previous usages of min(), max(), std::min(),
std::max(), SDL_min(), and SDL_max() with VVV_min() and VVV_max().
Additionally, there's no SDL_isxdigit(), so I just implemented my own
VVV_isxdigit().
SDL has SDL_malloc() and SDL_free(), but they have some refcounting
built in to them, so in order to use them with LodePNG, I have to
replace the malloc() and free() that LodePNG uses. Which isn't too hard,
I did it in a new file called ThirdPartyDeps.c, and LodePNG is now
compiled with the LODEPNG_NO_COMPILE_ALLOCATORS definition.
Lastly, I also refactored the awful strcpy() and strcat() usages in
PLATFORM_migrateSaveData() to use SDL_snprintf() instead. I know save
migration is getting axed in 2.4, but it still bothers me to have
something like that in the codebase otherwise.
Without further ado, here is the full list of functions that the
codebase now uses:
- SDL_strlcpy() instead of strcpy()
- SDL_strlcat() instead of strcat()
- SDL_snprintf() instead of sprintf(), strcpy(), or strcat() (see above)
- VVV_min() instead of min(), std::min(), or SDL_min()
- VVV_max() instead of max(), std::max(), or SDL_max()
- VVV_isxdigit() instead of isxdigit()
- SDL_strcmp() instead of strcmp()
- SDL_strcasecmp() instead of strcasecmp() or Win32 strcmpi()
- SDL_strstr() instead of strstr()
- SDL_strlen() instead of strlen()
- SDL_sscanf() instead of sscanf()
- SDL_getenv() instead of getenv()
- SDL_malloc() instead of malloc() (replacing in LodePNG as well)
- SDL_free() instead of free() (replacing in LodePNG as well)
2021-01-12 01:17:45 +01:00
|
|
|
if (_path.compare(0, SDL_strlen(levelDir), levelDir) != 0)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
_path = levelDir + _path;
|
|
|
|
}
|
|
|
|
|
2020-06-01 01:31:02 +02:00
|
|
|
FILESYSTEM_unmountassets();
|
2020-06-22 00:56:31 +02:00
|
|
|
if (game.playassets != "")
|
|
|
|
{
|
|
|
|
FILESYSTEM_mountassets(game.playassets.c_str());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
FILESYSTEM_mountassets(_path.c_str());
|
|
|
|
}
|
2020-06-01 01:31:02 +02:00
|
|
|
|
2020-06-17 00:45:01 +02:00
|
|
|
tinyxml2::XMLDocument doc;
|
2020-06-04 03:03:00 +02:00
|
|
|
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());
|
2020-06-02 12:59:54 +02:00
|
|
|
return false;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-11-03 18:55:26 +01:00
|
|
|
loaded_filepath = _path;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-06-04 03:03:00 +02:00
|
|
|
tinyxml2::XMLHandle hDoc(&doc);
|
|
|
|
tinyxml2::XMLElement* pElem;
|
|
|
|
tinyxml2::XMLHandle hRoot(NULL);
|
2020-01-01 21:29:24 +01:00
|
|
|
version = 0;
|
|
|
|
|
|
|
|
{
|
2020-06-04 03:03:00 +02:00
|
|
|
pElem=hDoc.FirstChildElement().ToElement();
|
2020-01-01 21:29:24 +01:00
|
|
|
// should always have a valid root but handle gracefully if it does
|
|
|
|
if (!pElem)
|
|
|
|
{
|
|
|
|
printf("No valid root! Corrupt level file?\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
pElem->QueryIntAttribute("version", &version);
|
|
|
|
// save this for later
|
2020-06-04 03:03:00 +02:00
|
|
|
hRoot=tinyxml2::XMLHandle(pElem);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-06-04 03:03:00 +02:00
|
|
|
for( pElem = hRoot.FirstChildElement( "Data" ).FirstChild().ToElement(); pElem; pElem=pElem->NextSiblingElement())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
std::string pKey(pElem->Value());
|
|
|
|
const char* pText = pElem->GetText() ;
|
|
|
|
if(pText == NULL)
|
|
|
|
{
|
|
|
|
pText = "";
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pKey == "MetaData")
|
|
|
|
{
|
|
|
|
|
2020-06-04 03:03:00 +02:00
|
|
|
for( tinyxml2::XMLElement* subElem = pElem->FirstChildElement(); subElem; subElem= subElem->NextSiblingElement())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Fix variables shadowing other variables
In C++, when you have two variables in different scopes with the same
name, the inner scope wins. Except you have to be really careful because
sometimes they're not (#507). So it's better to just always have unique
variable names and make sure to never clash a name with a variable in an
outer scope - after all, the C++ compiler and standard might be fine
with it, but that doesn't mean humans can't make mistakes reading or
writing it.
Usually I just renamed the inner variables, but for tx/ty in editor.cpp,
I just got rid of the ridiculous overcomplicated modulo calculations and
replaced them with actual simple modulo calculations, because the
existing ones were just ridiculous. Actually, somebody ought to find
every instance of the overcomplicated modulos and replace them with the
actual good ones, because it's really stupid, quite frankly...
2020-11-04 09:37:46 +01:00
|
|
|
std::string pKey_(subElem->Value());
|
|
|
|
const char* pText_ = subElem->GetText() ;
|
|
|
|
if(pText_ == NULL)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Fix variables shadowing other variables
In C++, when you have two variables in different scopes with the same
name, the inner scope wins. Except you have to be really careful because
sometimes they're not (#507). So it's better to just always have unique
variable names and make sure to never clash a name with a variable in an
outer scope - after all, the C++ compiler and standard might be fine
with it, but that doesn't mean humans can't make mistakes reading or
writing it.
Usually I just renamed the inner variables, but for tx/ty in editor.cpp,
I just got rid of the ridiculous overcomplicated modulo calculations and
replaced them with actual simple modulo calculations, because the
existing ones were just ridiculous. Actually, somebody ought to find
every instance of the overcomplicated modulos and replace them with the
actual good ones, because it's really stupid, quite frankly...
2020-11-04 09:37:46 +01:00
|
|
|
pText_ = "";
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
Fix variables shadowing other variables
In C++, when you have two variables in different scopes with the same
name, the inner scope wins. Except you have to be really careful because
sometimes they're not (#507). So it's better to just always have unique
variable names and make sure to never clash a name with a variable in an
outer scope - after all, the C++ compiler and standard might be fine
with it, but that doesn't mean humans can't make mistakes reading or
writing it.
Usually I just renamed the inner variables, but for tx/ty in editor.cpp,
I just got rid of the ridiculous overcomplicated modulo calculations and
replaced them with actual simple modulo calculations, because the
existing ones were just ridiculous. Actually, somebody ought to find
every instance of the overcomplicated modulos and replace them with the
actual good ones, because it's really stupid, quite frankly...
2020-11-04 09:37:46 +01:00
|
|
|
if(pKey_ == "Creator")
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Fix variables shadowing other variables
In C++, when you have two variables in different scopes with the same
name, the inner scope wins. Except you have to be really careful because
sometimes they're not (#507). So it's better to just always have unique
variable names and make sure to never clash a name with a variable in an
outer scope - after all, the C++ compiler and standard might be fine
with it, but that doesn't mean humans can't make mistakes reading or
writing it.
Usually I just renamed the inner variables, but for tx/ty in editor.cpp,
I just got rid of the ridiculous overcomplicated modulo calculations and
replaced them with actual simple modulo calculations, because the
existing ones were just ridiculous. Actually, somebody ought to find
every instance of the overcomplicated modulos and replace them with the
actual good ones, because it's really stupid, quite frankly...
2020-11-04 09:37:46 +01:00
|
|
|
EditorData::GetInstance().creator = pText_;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
Fix variables shadowing other variables
In C++, when you have two variables in different scopes with the same
name, the inner scope wins. Except you have to be really careful because
sometimes they're not (#507). So it's better to just always have unique
variable names and make sure to never clash a name with a variable in an
outer scope - after all, the C++ compiler and standard might be fine
with it, but that doesn't mean humans can't make mistakes reading or
writing it.
Usually I just renamed the inner variables, but for tx/ty in editor.cpp,
I just got rid of the ridiculous overcomplicated modulo calculations and
replaced them with actual simple modulo calculations, because the
existing ones were just ridiculous. Actually, somebody ought to find
every instance of the overcomplicated modulos and replace them with the
actual good ones, because it's really stupid, quite frankly...
2020-11-04 09:37:46 +01:00
|
|
|
if(pKey_ == "Title")
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Fix variables shadowing other variables
In C++, when you have two variables in different scopes with the same
name, the inner scope wins. Except you have to be really careful because
sometimes they're not (#507). So it's better to just always have unique
variable names and make sure to never clash a name with a variable in an
outer scope - after all, the C++ compiler and standard might be fine
with it, but that doesn't mean humans can't make mistakes reading or
writing it.
Usually I just renamed the inner variables, but for tx/ty in editor.cpp,
I just got rid of the ridiculous overcomplicated modulo calculations and
replaced them with actual simple modulo calculations, because the
existing ones were just ridiculous. Actually, somebody ought to find
every instance of the overcomplicated modulos and replace them with the
actual good ones, because it's really stupid, quite frankly...
2020-11-04 09:37:46 +01:00
|
|
|
EditorData::GetInstance().title = pText_;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
Fix variables shadowing other variables
In C++, when you have two variables in different scopes with the same
name, the inner scope wins. Except you have to be really careful because
sometimes they're not (#507). So it's better to just always have unique
variable names and make sure to never clash a name with a variable in an
outer scope - after all, the C++ compiler and standard might be fine
with it, but that doesn't mean humans can't make mistakes reading or
writing it.
Usually I just renamed the inner variables, but for tx/ty in editor.cpp,
I just got rid of the ridiculous overcomplicated modulo calculations and
replaced them with actual simple modulo calculations, because the
existing ones were just ridiculous. Actually, somebody ought to find
every instance of the overcomplicated modulos and replace them with the
actual good ones, because it's really stupid, quite frankly...
2020-11-04 09:37:46 +01:00
|
|
|
if(pKey_ == "Desc1")
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Fix variables shadowing other variables
In C++, when you have two variables in different scopes with the same
name, the inner scope wins. Except you have to be really careful because
sometimes they're not (#507). So it's better to just always have unique
variable names and make sure to never clash a name with a variable in an
outer scope - after all, the C++ compiler and standard might be fine
with it, but that doesn't mean humans can't make mistakes reading or
writing it.
Usually I just renamed the inner variables, but for tx/ty in editor.cpp,
I just got rid of the ridiculous overcomplicated modulo calculations and
replaced them with actual simple modulo calculations, because the
existing ones were just ridiculous. Actually, somebody ought to find
every instance of the overcomplicated modulos and replace them with the
actual good ones, because it's really stupid, quite frankly...
2020-11-04 09:37:46 +01:00
|
|
|
Desc1 = pText_;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
Fix variables shadowing other variables
In C++, when you have two variables in different scopes with the same
name, the inner scope wins. Except you have to be really careful because
sometimes they're not (#507). So it's better to just always have unique
variable names and make sure to never clash a name with a variable in an
outer scope - after all, the C++ compiler and standard might be fine
with it, but that doesn't mean humans can't make mistakes reading or
writing it.
Usually I just renamed the inner variables, but for tx/ty in editor.cpp,
I just got rid of the ridiculous overcomplicated modulo calculations and
replaced them with actual simple modulo calculations, because the
existing ones were just ridiculous. Actually, somebody ought to find
every instance of the overcomplicated modulos and replace them with the
actual good ones, because it's really stupid, quite frankly...
2020-11-04 09:37:46 +01:00
|
|
|
if(pKey_ == "Desc2")
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Fix variables shadowing other variables
In C++, when you have two variables in different scopes with the same
name, the inner scope wins. Except you have to be really careful because
sometimes they're not (#507). So it's better to just always have unique
variable names and make sure to never clash a name with a variable in an
outer scope - after all, the C++ compiler and standard might be fine
with it, but that doesn't mean humans can't make mistakes reading or
writing it.
Usually I just renamed the inner variables, but for tx/ty in editor.cpp,
I just got rid of the ridiculous overcomplicated modulo calculations and
replaced them with actual simple modulo calculations, because the
existing ones were just ridiculous. Actually, somebody ought to find
every instance of the overcomplicated modulos and replace them with the
actual good ones, because it's really stupid, quite frankly...
2020-11-04 09:37:46 +01:00
|
|
|
Desc2 = pText_;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
Fix variables shadowing other variables
In C++, when you have two variables in different scopes with the same
name, the inner scope wins. Except you have to be really careful because
sometimes they're not (#507). So it's better to just always have unique
variable names and make sure to never clash a name with a variable in an
outer scope - after all, the C++ compiler and standard might be fine
with it, but that doesn't mean humans can't make mistakes reading or
writing it.
Usually I just renamed the inner variables, but for tx/ty in editor.cpp,
I just got rid of the ridiculous overcomplicated modulo calculations and
replaced them with actual simple modulo calculations, because the
existing ones were just ridiculous. Actually, somebody ought to find
every instance of the overcomplicated modulos and replace them with the
actual good ones, because it's really stupid, quite frankly...
2020-11-04 09:37:46 +01:00
|
|
|
if(pKey_ == "Desc3")
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Fix variables shadowing other variables
In C++, when you have two variables in different scopes with the same
name, the inner scope wins. Except you have to be really careful because
sometimes they're not (#507). So it's better to just always have unique
variable names and make sure to never clash a name with a variable in an
outer scope - after all, the C++ compiler and standard might be fine
with it, but that doesn't mean humans can't make mistakes reading or
writing it.
Usually I just renamed the inner variables, but for tx/ty in editor.cpp,
I just got rid of the ridiculous overcomplicated modulo calculations and
replaced them with actual simple modulo calculations, because the
existing ones were just ridiculous. Actually, somebody ought to find
every instance of the overcomplicated modulos and replace them with the
actual good ones, because it's really stupid, quite frankly...
2020-11-04 09:37:46 +01:00
|
|
|
Desc3 = pText_;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
Fix variables shadowing other variables
In C++, when you have two variables in different scopes with the same
name, the inner scope wins. Except you have to be really careful because
sometimes they're not (#507). So it's better to just always have unique
variable names and make sure to never clash a name with a variable in an
outer scope - after all, the C++ compiler and standard might be fine
with it, but that doesn't mean humans can't make mistakes reading or
writing it.
Usually I just renamed the inner variables, but for tx/ty in editor.cpp,
I just got rid of the ridiculous overcomplicated modulo calculations and
replaced them with actual simple modulo calculations, because the
existing ones were just ridiculous. Actually, somebody ought to find
every instance of the overcomplicated modulos and replace them with the
actual good ones, because it's really stupid, quite frankly...
2020-11-04 09:37:46 +01:00
|
|
|
if(pKey_ == "website")
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Fix variables shadowing other variables
In C++, when you have two variables in different scopes with the same
name, the inner scope wins. Except you have to be really careful because
sometimes they're not (#507). So it's better to just always have unique
variable names and make sure to never clash a name with a variable in an
outer scope - after all, the C++ compiler and standard might be fine
with it, but that doesn't mean humans can't make mistakes reading or
writing it.
Usually I just renamed the inner variables, but for tx/ty in editor.cpp,
I just got rid of the ridiculous overcomplicated modulo calculations and
replaced them with actual simple modulo calculations, because the
existing ones were just ridiculous. Actually, somebody ought to find
every instance of the overcomplicated modulos and replace them with the
actual good ones, because it's really stupid, quite frankly...
2020-11-04 09:37:46 +01:00
|
|
|
website = pText_;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-06-30 23:08:14 +02:00
|
|
|
|
Fix variables shadowing other variables
In C++, when you have two variables in different scopes with the same
name, the inner scope wins. Except you have to be really careful because
sometimes they're not (#507). So it's better to just always have unique
variable names and make sure to never clash a name with a variable in an
outer scope - after all, the C++ compiler and standard might be fine
with it, but that doesn't mean humans can't make mistakes reading or
writing it.
Usually I just renamed the inner variables, but for tx/ty in editor.cpp,
I just got rid of the ridiculous overcomplicated modulo calculations and
replaced them with actual simple modulo calculations, because the
existing ones were just ridiculous. Actually, somebody ought to find
every instance of the overcomplicated modulos and replace them with the
actual good ones, because it's really stupid, quite frankly...
2020-11-04 09:37:46 +01:00
|
|
|
if(pKey_ == "onewaycol_override")
|
2020-06-30 23:08:14 +02:00
|
|
|
{
|
Fix variables shadowing other variables
In C++, when you have two variables in different scopes with the same
name, the inner scope wins. Except you have to be really careful because
sometimes they're not (#507). So it's better to just always have unique
variable names and make sure to never clash a name with a variable in an
outer scope - after all, the C++ compiler and standard might be fine
with it, but that doesn't mean humans can't make mistakes reading or
writing it.
Usually I just renamed the inner variables, but for tx/ty in editor.cpp,
I just got rid of the ridiculous overcomplicated modulo calculations and
replaced them with actual simple modulo calculations, because the
existing ones were just ridiculous. Actually, somebody ought to find
every instance of the overcomplicated modulos and replace them with the
actual good ones, because it's really stupid, quite frankly...
2020-11-04 09:37:46 +01:00
|
|
|
onewaycol_override = help.Int(pText_);
|
2020-06-30 23:08:14 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pKey == "mapwidth")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
mapwidth = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
if (pKey == "mapheight")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
mapheight = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
if (pKey == "levmusic")
|
|
|
|
{
|
2020-08-07 06:31:29 +02:00
|
|
|
levmusic = help.Int(pText);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-02-12 22:45:32 +01:00
|
|
|
if (pKey == "contents" && pText[0] != '\0')
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Refactor loading arrays from XML to not use the STL
The current way "arrays" from XML files are loaded (before this commit
is applied) goes something like this:
1. Read the buffer of the contents of the tag using TinyXML-2.
2. Allocate a buffer on the heap of the same size, and copy the
existing buffer to it. (This is what the statement `std::string
TextString = pText;` does.)
3. For each delimiter in the heap-allocated buffer...
a. Allocate another buffer on the heap, and copy the characters from
the previous delimiter to the delimiter you just hit.
b. Then allocate the buffer AGAIN, to copy it into an std::vector.
4. Then re-allocate every single buffer YET AGAIN, because you need to
make a copy of the std::vector in split() to return it to the caller.
As you can see, the existing way uses a lot of memory allocations and
data marshalling, just to split some text.
The problem here is mostly making a temporary std::vector of split text,
before doing any actual useful work (most likely, putting it into an
array or ANOTHER std::vector - if the latter, then that's yet another
memory allocation on top of the memory allocation you already did; this
memory allocation is unavoidable, unlike the ones mentioned earlier,
which should be removed).
So I noticed that since we're iterating over the entire string once
(just to shove its contents into a temporary std::vector), and then
basically iterating over it again - why can't the whole thing just be
more immediate, and just be iterated over once?
So that's what I've done here. I've axed the split() function (both of
them, actually), and made next_split() and next_split_s().
next_split() will take an existing string and a starting index, and it
will find the next occurrence of the given delimiter in the string. Once
it does so, it will return the length from the previous starting index,
and modify your starting index as well. The price for immediateness is
that you're supposed to handle keeping the index of the previous
starting index around in order to be able to use the function; updating
it after each iteration is also your responsibility.
(By the way, next_split() doesn't use SDL_strchr(), because we can't get
the length of the substring for the last substring. We could handle this
special case specifically, but it'd be uglier; it also introduces
iterating over the last substring twice, when we only need to do it
once.)
next_split_s() does the same thing as next_split(), except it will copy
the resulting substring into a buffer that you provide (along with its
size). Useful if you don't particularly care about the length of the
substring.
All callers have been updated accordingly. This new system does not make
ANY heap allocations at all; at worst, it allocates a temporary buffer
on the stack, but that's only if you use next_split_s(); plus, it'd be a
fixed-size buffer, and stack allocations are negligible anyway.
This improves performance when loading any sort of XML file, especially
loading custom levels - which, on my system at least, I can noticeably
tell (there's less of a freeze when I load in to a custom level with
lots of scripts). It also decreases memory usage, because the heap isn't
being used just to iterate over some delimiters when XML files are
loaded.
2021-02-13 01:37:29 +01:00
|
|
|
int x = 0;
|
|
|
|
int y = 0;
|
|
|
|
|
|
|
|
char buffer[16];
|
|
|
|
size_t start = 0;
|
|
|
|
|
|
|
|
while (next_split_s(buffer, sizeof(buffer), &start, pText, ','))
|
2021-02-12 22:53:01 +01:00
|
|
|
{
|
Refactor loading arrays from XML to not use the STL
The current way "arrays" from XML files are loaded (before this commit
is applied) goes something like this:
1. Read the buffer of the contents of the tag using TinyXML-2.
2. Allocate a buffer on the heap of the same size, and copy the
existing buffer to it. (This is what the statement `std::string
TextString = pText;` does.)
3. For each delimiter in the heap-allocated buffer...
a. Allocate another buffer on the heap, and copy the characters from
the previous delimiter to the delimiter you just hit.
b. Then allocate the buffer AGAIN, to copy it into an std::vector.
4. Then re-allocate every single buffer YET AGAIN, because you need to
make a copy of the std::vector in split() to return it to the caller.
As you can see, the existing way uses a lot of memory allocations and
data marshalling, just to split some text.
The problem here is mostly making a temporary std::vector of split text,
before doing any actual useful work (most likely, putting it into an
array or ANOTHER std::vector - if the latter, then that's yet another
memory allocation on top of the memory allocation you already did; this
memory allocation is unavoidable, unlike the ones mentioned earlier,
which should be removed).
So I noticed that since we're iterating over the entire string once
(just to shove its contents into a temporary std::vector), and then
basically iterating over it again - why can't the whole thing just be
more immediate, and just be iterated over once?
So that's what I've done here. I've axed the split() function (both of
them, actually), and made next_split() and next_split_s().
next_split() will take an existing string and a starting index, and it
will find the next occurrence of the given delimiter in the string. Once
it does so, it will return the length from the previous starting index,
and modify your starting index as well. The price for immediateness is
that you're supposed to handle keeping the index of the previous
starting index around in order to be able to use the function; updating
it after each iteration is also your responsibility.
(By the way, next_split() doesn't use SDL_strchr(), because we can't get
the length of the substring for the last substring. We could handle this
special case specifically, but it'd be uglier; it also introduces
iterating over the last substring twice, when we only need to do it
once.)
next_split_s() does the same thing as next_split(), except it will copy
the resulting substring into a buffer that you provide (along with its
size). Useful if you don't particularly care about the length of the
substring.
All callers have been updated accordingly. This new system does not make
ANY heap allocations at all; at worst, it allocates a temporary buffer
on the stack, but that's only if you use next_split_s(); plus, it'd be a
fixed-size buffer, and stack allocations are negligible anyway.
This improves performance when loading any sort of XML file, especially
loading custom levels - which, on my system at least, I can noticeably
tell (there's less of a freeze when I load in to a custom level with
lots of scripts). It also decreases memory usage, because the heap isn't
being used just to iterate over some delimiters when XML files are
loaded.
2021-02-13 01:37:29 +01:00
|
|
|
const int idx = x + maxwidth*40*y;
|
|
|
|
|
|
|
|
if (INBOUNDS_ARR(idx, contents))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Refactor loading arrays from XML to not use the STL
The current way "arrays" from XML files are loaded (before this commit
is applied) goes something like this:
1. Read the buffer of the contents of the tag using TinyXML-2.
2. Allocate a buffer on the heap of the same size, and copy the
existing buffer to it. (This is what the statement `std::string
TextString = pText;` does.)
3. For each delimiter in the heap-allocated buffer...
a. Allocate another buffer on the heap, and copy the characters from
the previous delimiter to the delimiter you just hit.
b. Then allocate the buffer AGAIN, to copy it into an std::vector.
4. Then re-allocate every single buffer YET AGAIN, because you need to
make a copy of the std::vector in split() to return it to the caller.
As you can see, the existing way uses a lot of memory allocations and
data marshalling, just to split some text.
The problem here is mostly making a temporary std::vector of split text,
before doing any actual useful work (most likely, putting it into an
array or ANOTHER std::vector - if the latter, then that's yet another
memory allocation on top of the memory allocation you already did; this
memory allocation is unavoidable, unlike the ones mentioned earlier,
which should be removed).
So I noticed that since we're iterating over the entire string once
(just to shove its contents into a temporary std::vector), and then
basically iterating over it again - why can't the whole thing just be
more immediate, and just be iterated over once?
So that's what I've done here. I've axed the split() function (both of
them, actually), and made next_split() and next_split_s().
next_split() will take an existing string and a starting index, and it
will find the next occurrence of the given delimiter in the string. Once
it does so, it will return the length from the previous starting index,
and modify your starting index as well. The price for immediateness is
that you're supposed to handle keeping the index of the previous
starting index around in order to be able to use the function; updating
it after each iteration is also your responsibility.
(By the way, next_split() doesn't use SDL_strchr(), because we can't get
the length of the substring for the last substring. We could handle this
special case specifically, but it'd be uglier; it also introduces
iterating over the last substring twice, when we only need to do it
once.)
next_split_s() does the same thing as next_split(), except it will copy
the resulting substring into a buffer that you provide (along with its
size). Useful if you don't particularly care about the length of the
substring.
All callers have been updated accordingly. This new system does not make
ANY heap allocations at all; at worst, it allocates a temporary buffer
on the stack, but that's only if you use next_split_s(); plus, it'd be a
fixed-size buffer, and stack allocations are negligible anyway.
This improves performance when loading any sort of XML file, especially
loading custom levels - which, on my system at least, I can noticeably
tell (there's less of a freeze when I load in to a custom level with
lots of scripts). It also decreases memory usage, because the heap isn't
being used just to iterate over some delimiters when XML files are
loaded.
2021-02-13 01:37:29 +01:00
|
|
|
contents[idx] = help.Int(buffer);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2021-02-12 22:53:01 +01:00
|
|
|
|
Refactor loading arrays from XML to not use the STL
The current way "arrays" from XML files are loaded (before this commit
is applied) goes something like this:
1. Read the buffer of the contents of the tag using TinyXML-2.
2. Allocate a buffer on the heap of the same size, and copy the
existing buffer to it. (This is what the statement `std::string
TextString = pText;` does.)
3. For each delimiter in the heap-allocated buffer...
a. Allocate another buffer on the heap, and copy the characters from
the previous delimiter to the delimiter you just hit.
b. Then allocate the buffer AGAIN, to copy it into an std::vector.
4. Then re-allocate every single buffer YET AGAIN, because you need to
make a copy of the std::vector in split() to return it to the caller.
As you can see, the existing way uses a lot of memory allocations and
data marshalling, just to split some text.
The problem here is mostly making a temporary std::vector of split text,
before doing any actual useful work (most likely, putting it into an
array or ANOTHER std::vector - if the latter, then that's yet another
memory allocation on top of the memory allocation you already did; this
memory allocation is unavoidable, unlike the ones mentioned earlier,
which should be removed).
So I noticed that since we're iterating over the entire string once
(just to shove its contents into a temporary std::vector), and then
basically iterating over it again - why can't the whole thing just be
more immediate, and just be iterated over once?
So that's what I've done here. I've axed the split() function (both of
them, actually), and made next_split() and next_split_s().
next_split() will take an existing string and a starting index, and it
will find the next occurrence of the given delimiter in the string. Once
it does so, it will return the length from the previous starting index,
and modify your starting index as well. The price for immediateness is
that you're supposed to handle keeping the index of the previous
starting index around in order to be able to use the function; updating
it after each iteration is also your responsibility.
(By the way, next_split() doesn't use SDL_strchr(), because we can't get
the length of the substring for the last substring. We could handle this
special case specifically, but it'd be uglier; it also introduces
iterating over the last substring twice, when we only need to do it
once.)
next_split_s() does the same thing as next_split(), except it will copy
the resulting substring into a buffer that you provide (along with its
size). Useful if you don't particularly care about the length of the
substring.
All callers have been updated accordingly. This new system does not make
ANY heap allocations at all; at worst, it allocates a temporary buffer
on the stack, but that's only if you use next_split_s(); plus, it'd be a
fixed-size buffer, and stack allocations are negligible anyway.
This improves performance when loading any sort of XML file, especially
loading custom levels - which, on my system at least, I can noticeably
tell (there's less of a freeze when I load in to a custom level with
lots of scripts). It also decreases memory usage, because the heap isn't
being used just to iterate over some delimiters when XML files are
loaded.
2021-02-13 01:37:29 +01:00
|
|
|
++x;
|
|
|
|
|
|
|
|
if (x == mapwidth*40)
|
|
|
|
{
|
|
|
|
x = 0;
|
|
|
|
++y;
|
|
|
|
}
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (pKey == "edEntities")
|
|
|
|
{
|
2020-06-04 03:03:00 +02:00
|
|
|
for( tinyxml2::XMLElement* edEntityEl = pElem->FirstChildElement(); edEntityEl; edEntityEl=edEntityEl->NextSiblingElement())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-01 21:24:43 +01:00
|
|
|
edentities entity;
|
2021-02-27 02:46:20 +01:00
|
|
|
const char* text = edEntityEl->GetText();
|
2020-03-01 21:24:43 +01:00
|
|
|
|
2021-02-27 02:46:20 +01:00
|
|
|
if (text != NULL)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2021-02-27 02:46:20 +01:00
|
|
|
size_t len = SDL_strlen(text);
|
2020-06-17 01:19:33 +02:00
|
|
|
|
|
|
|
// 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 ...>
|
|
|
|
//    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.
|
|
|
|
|
2021-02-27 02:46:20 +01:00
|
|
|
if (endsWith(text, "\n ")) // linefeed + exactly 12 spaces
|
2020-06-17 01:19:33 +02:00
|
|
|
{
|
|
|
|
// 12 spaces + 1 linefeed = 13 chars
|
2021-02-27 02:46:20 +01:00
|
|
|
len -= 13;
|
2020-06-17 01:19:33 +02:00
|
|
|
}
|
|
|
|
|
2021-02-27 02:46:20 +01:00
|
|
|
entity.scriptname = std::string(text, len);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-01 21:24:43 +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;
|
2020-06-04 03:03:00 +02:00
|
|
|
for( tinyxml2::XMLElement* edLevelClassElement = pElem->FirstChildElement(); edLevelClassElement; edLevelClassElement=edLevelClassElement->NextSiblingElement())
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
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++;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-12 22:45:32 +01:00
|
|
|
if (pKey == "script" && pText[0] != '\0')
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2021-02-12 22:53:01 +01:00
|
|
|
Script script_;
|
|
|
|
bool headerfound = false;
|
Refactor custom scripts to not be stored in one giant vector of lines
This commit refactors custom level scripts to no longer be stored in one
giant vector containing not only every single script name, but every
single script's contents as well. More specifically,
scriptclass::customscript has been converted to an std::vector<Script>
scriptclass::customscripts (note the extra S), and a Script is just a
struct with an std::string name and std::vector<std::string> contents.
This is an improvement in both performance and maintainability. The game
no longer has to look through script contents in case they're actually
script names, and then manually extract the script contents from there.
Instead, all it has to do is look for script names only. And the
contents are provided for free. This results in a performance gain.
Also, the old system resulted in lots of boilerplate everywhere anytime
scripts had to be handled or parsed. Now, the boilerplate is only done
when saving or loading a custom level. This makes code quality much,
much better.
To be sure I didn't actually change anything, I tested by first saving
Dimension Open in current 2.3 (because current 2.3 gets rid of the
awful edentity whitespace), and then resaved it on this patch. There is
absolutely no difference between the current-2.3-resave and
this-patch-resave.
2020-06-12 02:31:57 +02:00
|
|
|
|
Refactor loading arrays from XML to not use the STL
The current way "arrays" from XML files are loaded (before this commit
is applied) goes something like this:
1. Read the buffer of the contents of the tag using TinyXML-2.
2. Allocate a buffer on the heap of the same size, and copy the
existing buffer to it. (This is what the statement `std::string
TextString = pText;` does.)
3. For each delimiter in the heap-allocated buffer...
a. Allocate another buffer on the heap, and copy the characters from
the previous delimiter to the delimiter you just hit.
b. Then allocate the buffer AGAIN, to copy it into an std::vector.
4. Then re-allocate every single buffer YET AGAIN, because you need to
make a copy of the std::vector in split() to return it to the caller.
As you can see, the existing way uses a lot of memory allocations and
data marshalling, just to split some text.
The problem here is mostly making a temporary std::vector of split text,
before doing any actual useful work (most likely, putting it into an
array or ANOTHER std::vector - if the latter, then that's yet another
memory allocation on top of the memory allocation you already did; this
memory allocation is unavoidable, unlike the ones mentioned earlier,
which should be removed).
So I noticed that since we're iterating over the entire string once
(just to shove its contents into a temporary std::vector), and then
basically iterating over it again - why can't the whole thing just be
more immediate, and just be iterated over once?
So that's what I've done here. I've axed the split() function (both of
them, actually), and made next_split() and next_split_s().
next_split() will take an existing string and a starting index, and it
will find the next occurrence of the given delimiter in the string. Once
it does so, it will return the length from the previous starting index,
and modify your starting index as well. The price for immediateness is
that you're supposed to handle keeping the index of the previous
starting index around in order to be able to use the function; updating
it after each iteration is also your responsibility.
(By the way, next_split() doesn't use SDL_strchr(), because we can't get
the length of the substring for the last substring. We could handle this
special case specifically, but it'd be uglier; it also introduces
iterating over the last substring twice, when we only need to do it
once.)
next_split_s() does the same thing as next_split(), except it will copy
the resulting substring into a buffer that you provide (along with its
size). Useful if you don't particularly care about the length of the
substring.
All callers have been updated accordingly. This new system does not make
ANY heap allocations at all; at worst, it allocates a temporary buffer
on the stack, but that's only if you use next_split_s(); plus, it'd be a
fixed-size buffer, and stack allocations are negligible anyway.
This improves performance when loading any sort of XML file, especially
loading custom levels - which, on my system at least, I can noticeably
tell (there's less of a freeze when I load in to a custom level with
lots of scripts). It also decreases memory usage, because the heap isn't
being used just to iterate over some delimiters when XML files are
loaded.
2021-02-13 01:37:29 +01:00
|
|
|
size_t start = 0;
|
|
|
|
size_t len = 0;
|
|
|
|
size_t prev_start = 0;
|
|
|
|
|
|
|
|
while (next_split(&start, &len, &pText[start], '|'))
|
|
|
|
{
|
|
|
|
if (len > 0 && pText[prev_start + len - 1] == ':')
|
2021-02-12 22:53:01 +01:00
|
|
|
{
|
2021-02-13 01:25:32 +01:00
|
|
|
if (headerfound)
|
Refactor custom scripts to not be stored in one giant vector of lines
This commit refactors custom level scripts to no longer be stored in one
giant vector containing not only every single script name, but every
single script's contents as well. More specifically,
scriptclass::customscript has been converted to an std::vector<Script>
scriptclass::customscripts (note the extra S), and a Script is just a
struct with an std::string name and std::vector<std::string> contents.
This is an improvement in both performance and maintainability. The game
no longer has to look through script contents in case they're actually
script names, and then manually extract the script contents from there.
Instead, all it has to do is look for script names only. And the
contents are provided for free. This results in a performance gain.
Also, the old system resulted in lots of boilerplate everywhere anytime
scripts had to be handled or parsed. Now, the boilerplate is only done
when saving or loading a custom level. This makes code quality much,
much better.
To be sure I didn't actually change anything, I tested by first saving
Dimension Open in current 2.3 (because current 2.3 gets rid of the
awful edentity whitespace), and then resaved it on this patch. There is
absolutely no difference between the current-2.3-resave and
this-patch-resave.
2020-06-12 02:31:57 +02:00
|
|
|
{
|
2021-02-12 22:53:01 +01:00
|
|
|
script.customscripts.push_back(script_);
|
Refactor custom scripts to not be stored in one giant vector of lines
This commit refactors custom level scripts to no longer be stored in one
giant vector containing not only every single script name, but every
single script's contents as well. More specifically,
scriptclass::customscript has been converted to an std::vector<Script>
scriptclass::customscripts (note the extra S), and a Script is just a
struct with an std::string name and std::vector<std::string> contents.
This is an improvement in both performance and maintainability. The game
no longer has to look through script contents in case they're actually
script names, and then manually extract the script contents from there.
Instead, all it has to do is look for script names only. And the
contents are provided for free. This results in a performance gain.
Also, the old system resulted in lots of boilerplate everywhere anytime
scripts had to be handled or parsed. Now, the boilerplate is only done
when saving or loading a custom level. This makes code quality much,
much better.
To be sure I didn't actually change anything, I tested by first saving
Dimension Open in current 2.3 (because current 2.3 gets rid of the
awful edentity whitespace), and then resaved it on this patch. There is
absolutely no difference between the current-2.3-resave and
this-patch-resave.
2020-06-12 02:31:57 +02:00
|
|
|
}
|
Refactor loading arrays from XML to not use the STL
The current way "arrays" from XML files are loaded (before this commit
is applied) goes something like this:
1. Read the buffer of the contents of the tag using TinyXML-2.
2. Allocate a buffer on the heap of the same size, and copy the
existing buffer to it. (This is what the statement `std::string
TextString = pText;` does.)
3. For each delimiter in the heap-allocated buffer...
a. Allocate another buffer on the heap, and copy the characters from
the previous delimiter to the delimiter you just hit.
b. Then allocate the buffer AGAIN, to copy it into an std::vector.
4. Then re-allocate every single buffer YET AGAIN, because you need to
make a copy of the std::vector in split() to return it to the caller.
As you can see, the existing way uses a lot of memory allocations and
data marshalling, just to split some text.
The problem here is mostly making a temporary std::vector of split text,
before doing any actual useful work (most likely, putting it into an
array or ANOTHER std::vector - if the latter, then that's yet another
memory allocation on top of the memory allocation you already did; this
memory allocation is unavoidable, unlike the ones mentioned earlier,
which should be removed).
So I noticed that since we're iterating over the entire string once
(just to shove its contents into a temporary std::vector), and then
basically iterating over it again - why can't the whole thing just be
more immediate, and just be iterated over once?
So that's what I've done here. I've axed the split() function (both of
them, actually), and made next_split() and next_split_s().
next_split() will take an existing string and a starting index, and it
will find the next occurrence of the given delimiter in the string. Once
it does so, it will return the length from the previous starting index,
and modify your starting index as well. The price for immediateness is
that you're supposed to handle keeping the index of the previous
starting index around in order to be able to use the function; updating
it after each iteration is also your responsibility.
(By the way, next_split() doesn't use SDL_strchr(), because we can't get
the length of the substring for the last substring. We could handle this
special case specifically, but it'd be uglier; it also introduces
iterating over the last substring twice, when we only need to do it
once.)
next_split_s() does the same thing as next_split(), except it will copy
the resulting substring into a buffer that you provide (along with its
size). Useful if you don't particularly care about the length of the
substring.
All callers have been updated accordingly. This new system does not make
ANY heap allocations at all; at worst, it allocates a temporary buffer
on the stack, but that's only if you use next_split_s(); plus, it'd be a
fixed-size buffer, and stack allocations are negligible anyway.
This improves performance when loading any sort of XML file, especially
loading custom levels - which, on my system at least, I can noticeably
tell (there's less of a freeze when I load in to a custom level with
lots of scripts). It also decreases memory usage, because the heap isn't
being used just to iterate over some delimiters when XML files are
loaded.
2021-02-13 01:37:29 +01:00
|
|
|
|
|
|
|
script_.name = std::string(&pText[prev_start], len - 1);
|
2021-02-12 22:53:01 +01:00
|
|
|
script_.contents.clear();
|
|
|
|
headerfound = true;
|
Refactor loading arrays from XML to not use the STL
The current way "arrays" from XML files are loaded (before this commit
is applied) goes something like this:
1. Read the buffer of the contents of the tag using TinyXML-2.
2. Allocate a buffer on the heap of the same size, and copy the
existing buffer to it. (This is what the statement `std::string
TextString = pText;` does.)
3. For each delimiter in the heap-allocated buffer...
a. Allocate another buffer on the heap, and copy the characters from
the previous delimiter to the delimiter you just hit.
b. Then allocate the buffer AGAIN, to copy it into an std::vector.
4. Then re-allocate every single buffer YET AGAIN, because you need to
make a copy of the std::vector in split() to return it to the caller.
As you can see, the existing way uses a lot of memory allocations and
data marshalling, just to split some text.
The problem here is mostly making a temporary std::vector of split text,
before doing any actual useful work (most likely, putting it into an
array or ANOTHER std::vector - if the latter, then that's yet another
memory allocation on top of the memory allocation you already did; this
memory allocation is unavoidable, unlike the ones mentioned earlier,
which should be removed).
So I noticed that since we're iterating over the entire string once
(just to shove its contents into a temporary std::vector), and then
basically iterating over it again - why can't the whole thing just be
more immediate, and just be iterated over once?
So that's what I've done here. I've axed the split() function (both of
them, actually), and made next_split() and next_split_s().
next_split() will take an existing string and a starting index, and it
will find the next occurrence of the given delimiter in the string. Once
it does so, it will return the length from the previous starting index,
and modify your starting index as well. The price for immediateness is
that you're supposed to handle keeping the index of the previous
starting index around in order to be able to use the function; updating
it after each iteration is also your responsibility.
(By the way, next_split() doesn't use SDL_strchr(), because we can't get
the length of the substring for the last substring. We could handle this
special case specifically, but it'd be uglier; it also introduces
iterating over the last substring twice, when we only need to do it
once.)
next_split_s() does the same thing as next_split(), except it will copy
the resulting substring into a buffer that you provide (along with its
size). Useful if you don't particularly care about the length of the
substring.
All callers have been updated accordingly. This new system does not make
ANY heap allocations at all; at worst, it allocates a temporary buffer
on the stack, but that's only if you use next_split_s(); plus, it'd be a
fixed-size buffer, and stack allocations are negligible anyway.
This improves performance when loading any sort of XML file, especially
loading custom levels - which, on my system at least, I can noticeably
tell (there's less of a freeze when I load in to a custom level with
lots of scripts). It also decreases memory usage, because the heap isn't
being used just to iterate over some delimiters when XML files are
loaded.
2021-02-13 01:37:29 +01:00
|
|
|
|
|
|
|
goto next;
|
Refactor custom scripts to not be stored in one giant vector of lines
This commit refactors custom level scripts to no longer be stored in one
giant vector containing not only every single script name, but every
single script's contents as well. More specifically,
scriptclass::customscript has been converted to an std::vector<Script>
scriptclass::customscripts (note the extra S), and a Script is just a
struct with an std::string name and std::vector<std::string> contents.
This is an improvement in both performance and maintainability. The game
no longer has to look through script contents in case they're actually
script names, and then manually extract the script contents from there.
Instead, all it has to do is look for script names only. And the
contents are provided for free. This results in a performance gain.
Also, the old system resulted in lots of boilerplate everywhere anytime
scripts had to be handled or parsed. Now, the boilerplate is only done
when saving or loading a custom level. This makes code quality much,
much better.
To be sure I didn't actually change anything, I tested by first saving
Dimension Open in current 2.3 (because current 2.3 gets rid of the
awful edentity whitespace), and then resaved it on this patch. There is
absolutely no difference between the current-2.3-resave and
this-patch-resave.
2020-06-12 02:31:57 +02:00
|
|
|
}
|
2021-02-12 22:53:01 +01:00
|
|
|
|
2021-02-13 01:25:32 +01:00
|
|
|
if (headerfound)
|
Refactor custom scripts to not be stored in one giant vector of lines
This commit refactors custom level scripts to no longer be stored in one
giant vector containing not only every single script name, but every
single script's contents as well. More specifically,
scriptclass::customscript has been converted to an std::vector<Script>
scriptclass::customscripts (note the extra S), and a Script is just a
struct with an std::string name and std::vector<std::string> contents.
This is an improvement in both performance and maintainability. The game
no longer has to look through script contents in case they're actually
script names, and then manually extract the script contents from there.
Instead, all it has to do is look for script names only. And the
contents are provided for free. This results in a performance gain.
Also, the old system resulted in lots of boilerplate everywhere anytime
scripts had to be handled or parsed. Now, the boilerplate is only done
when saving or loading a custom level. This makes code quality much,
much better.
To be sure I didn't actually change anything, I tested by first saving
Dimension Open in current 2.3 (because current 2.3 gets rid of the
awful edentity whitespace), and then resaved it on this patch. There is
absolutely no difference between the current-2.3-resave and
this-patch-resave.
2020-06-12 02:31:57 +02:00
|
|
|
{
|
Refactor loading arrays from XML to not use the STL
The current way "arrays" from XML files are loaded (before this commit
is applied) goes something like this:
1. Read the buffer of the contents of the tag using TinyXML-2.
2. Allocate a buffer on the heap of the same size, and copy the
existing buffer to it. (This is what the statement `std::string
TextString = pText;` does.)
3. For each delimiter in the heap-allocated buffer...
a. Allocate another buffer on the heap, and copy the characters from
the previous delimiter to the delimiter you just hit.
b. Then allocate the buffer AGAIN, to copy it into an std::vector.
4. Then re-allocate every single buffer YET AGAIN, because you need to
make a copy of the std::vector in split() to return it to the caller.
As you can see, the existing way uses a lot of memory allocations and
data marshalling, just to split some text.
The problem here is mostly making a temporary std::vector of split text,
before doing any actual useful work (most likely, putting it into an
array or ANOTHER std::vector - if the latter, then that's yet another
memory allocation on top of the memory allocation you already did; this
memory allocation is unavoidable, unlike the ones mentioned earlier,
which should be removed).
So I noticed that since we're iterating over the entire string once
(just to shove its contents into a temporary std::vector), and then
basically iterating over it again - why can't the whole thing just be
more immediate, and just be iterated over once?
So that's what I've done here. I've axed the split() function (both of
them, actually), and made next_split() and next_split_s().
next_split() will take an existing string and a starting index, and it
will find the next occurrence of the given delimiter in the string. Once
it does so, it will return the length from the previous starting index,
and modify your starting index as well. The price for immediateness is
that you're supposed to handle keeping the index of the previous
starting index around in order to be able to use the function; updating
it after each iteration is also your responsibility.
(By the way, next_split() doesn't use SDL_strchr(), because we can't get
the length of the substring for the last substring. We could handle this
special case specifically, but it'd be uglier; it also introduces
iterating over the last substring twice, when we only need to do it
once.)
next_split_s() does the same thing as next_split(), except it will copy
the resulting substring into a buffer that you provide (along with its
size). Useful if you don't particularly care about the length of the
substring.
All callers have been updated accordingly. This new system does not make
ANY heap allocations at all; at worst, it allocates a temporary buffer
on the stack, but that's only if you use next_split_s(); plus, it'd be a
fixed-size buffer, and stack allocations are negligible anyway.
This improves performance when loading any sort of XML file, especially
loading custom levels - which, on my system at least, I can noticeably
tell (there's less of a freeze when I load in to a custom level with
lots of scripts). It also decreases memory usage, because the heap isn't
being used just to iterate over some delimiters when XML files are
loaded.
2021-02-13 01:37:29 +01:00
|
|
|
script_.contents.push_back(std::string(&pText[prev_start], len));
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
Refactor loading arrays from XML to not use the STL
The current way "arrays" from XML files are loaded (before this commit
is applied) goes something like this:
1. Read the buffer of the contents of the tag using TinyXML-2.
2. Allocate a buffer on the heap of the same size, and copy the
existing buffer to it. (This is what the statement `std::string
TextString = pText;` does.)
3. For each delimiter in the heap-allocated buffer...
a. Allocate another buffer on the heap, and copy the characters from
the previous delimiter to the delimiter you just hit.
b. Then allocate the buffer AGAIN, to copy it into an std::vector.
4. Then re-allocate every single buffer YET AGAIN, because you need to
make a copy of the std::vector in split() to return it to the caller.
As you can see, the existing way uses a lot of memory allocations and
data marshalling, just to split some text.
The problem here is mostly making a temporary std::vector of split text,
before doing any actual useful work (most likely, putting it into an
array or ANOTHER std::vector - if the latter, then that's yet another
memory allocation on top of the memory allocation you already did; this
memory allocation is unavoidable, unlike the ones mentioned earlier,
which should be removed).
So I noticed that since we're iterating over the entire string once
(just to shove its contents into a temporary std::vector), and then
basically iterating over it again - why can't the whole thing just be
more immediate, and just be iterated over once?
So that's what I've done here. I've axed the split() function (both of
them, actually), and made next_split() and next_split_s().
next_split() will take an existing string and a starting index, and it
will find the next occurrence of the given delimiter in the string. Once
it does so, it will return the length from the previous starting index,
and modify your starting index as well. The price for immediateness is
that you're supposed to handle keeping the index of the previous
starting index around in order to be able to use the function; updating
it after each iteration is also your responsibility.
(By the way, next_split() doesn't use SDL_strchr(), because we can't get
the length of the substring for the last substring. We could handle this
special case specifically, but it'd be uglier; it also introduces
iterating over the last substring twice, when we only need to do it
once.)
next_split_s() does the same thing as next_split(), except it will copy
the resulting substring into a buffer that you provide (along with its
size). Useful if you don't particularly care about the length of the
substring.
All callers have been updated accordingly. This new system does not make
ANY heap allocations at all; at worst, it allocates a temporary buffer
on the stack, but that's only if you use next_split_s(); plus, it'd be a
fixed-size buffer, and stack allocations are negligible anyway.
This improves performance when loading any sort of XML file, especially
loading custom levels - which, on my system at least, I can noticeably
tell (there's less of a freeze when I load in to a custom level with
lots of scripts). It also decreases memory usage, because the heap isn't
being used just to iterate over some delimiters when XML files are
loaded.
2021-02-13 01:37:29 +01:00
|
|
|
|
|
|
|
next:
|
|
|
|
prev_start = start;
|
2021-02-12 22:53:01 +01:00
|
|
|
}
|
2021-02-13 01:25:32 +01:00
|
|
|
|
2021-02-13 01:29:08 +01:00
|
|
|
/* Add the last script */
|
2021-02-13 01:25:32 +01:00
|
|
|
if (headerfound)
|
2021-02-12 22:53:01 +01:00
|
|
|
{
|
|
|
|
script.customscripts.push_back(script_);
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
gethooks();
|
|
|
|
version=2;
|
2020-06-02 12:59:54 +02:00
|
|
|
|
|
|
|
return true;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-06-02 12:59:54 +02:00
|
|
|
bool editorclass::save(std::string& _path)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-06-04 03:12:12 +02:00
|
|
|
tinyxml2::XMLDocument doc;
|
2020-11-03 18:55:26 +01:00
|
|
|
|
|
|
|
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())
|
2020-09-25 18:31:03 +02:00
|
|
|
{
|
2020-11-03 18:55:26 +01:00
|
|
|
printf("Currently-loaded %s not found\n", loaded_filepath.c_str());
|
2020-09-25 18:31:03 +02:00
|
|
|
}
|
2020-11-03 18:55:26 +01:00
|
|
|
|
|
|
|
loaded_filepath = newpath;
|
|
|
|
|
2020-06-04 03:12:12 +02:00
|
|
|
tinyxml2::XMLElement* msg;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:31:03 +02: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);
|
|
|
|
|
2020-09-25 18:31:03 +02:00
|
|
|
xml::update_comment(root, " Save file ");
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:31:03 +02:00
|
|
|
tinyxml2::XMLElement * data = xml::update_element(root, "Data");
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:31:03 +02:00
|
|
|
msg = xml::update_element(data, "MetaData");
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
//getUser
|
2020-09-25 18:31:03 +02:00
|
|
|
xml::update_tag(msg, "Creator", EditorData::GetInstance().creator.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:31:03 +02:00
|
|
|
xml::update_tag(msg, "Title", EditorData::GetInstance().title.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:31:03 +02:00
|
|
|
xml::update_tag(msg, "Created", version);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:31:03 +02:00
|
|
|
xml::update_tag(msg, "Modified", EditorData::GetInstance().modifier.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:31:03 +02:00
|
|
|
xml::update_tag(msg, "Modifiers", version);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:31:03 +02:00
|
|
|
xml::update_tag(msg, "Desc1", Desc1.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:31:03 +02:00
|
|
|
xml::update_tag(msg, "Desc2", Desc2.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:31:03 +02:00
|
|
|
xml::update_tag(msg, "Desc3", Desc3.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:31:03 +02:00
|
|
|
xml::update_tag(msg, "website", website.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-06-30 23:08:14 +02:00
|
|
|
if (onewaycol_override)
|
|
|
|
{
|
2020-09-25 18:31:03 +02:00
|
|
|
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);
|
|
|
|
}
|
2020-06-30 23:08:14 +02:00
|
|
|
}
|
|
|
|
|
2020-09-25 18:31:03 +02:00
|
|
|
xml::update_tag(data, "mapwidth", mapwidth);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:31:03 +02:00
|
|
|
xml::update_tag(data, "mapheight", mapheight);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-09-25 18:31:03 +02: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++ )
|
|
|
|
{
|
2020-04-02 21:30:37 +02:00
|
|
|
contentsString += help.String(contents[x + (maxwidth*40*y)]) + ",";
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
2020-09-25 18:31:03 +02:00
|
|
|
xml::update_tag(data, "contents", contentsString.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
|
2020-09-25 18:31:03 +02:00
|
|
|
msg = xml::update_element_delete_contents(data, "edEntities");
|
2020-03-01 21:24:43 +01:00
|
|
|
for(size_t i = 0; i < edentity.size(); i++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-06-04 03:12:12 +02: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);
|
2020-06-04 03:12:12 +02:00
|
|
|
edentityElement->LinkEndChild( doc.NewText( edentity[i].scriptname.c_str() )) ;
|
2020-01-01 21:29:24 +01:00
|
|
|
msg->LinkEndChild( edentityElement );
|
|
|
|
}
|
|
|
|
|
2020-09-25 18:31:03 +02:00
|
|
|
msg = xml::update_element_delete_contents(data, "levelMetaData");
|
2020-09-10 07:13:59 +02:00
|
|
|
for(size_t i = 0; i < SDL_arraysize(level); i++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-06-04 03:12:12 +02: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);
|
|
|
|
|
2020-06-04 03:12:12 +02:00
|
|
|
edlevelclassElement->LinkEndChild( doc.NewText( level[i].roomname.c_str() )) ;
|
2020-01-01 21:29:24 +01:00
|
|
|
msg->LinkEndChild( edlevelclassElement );
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string scriptString;
|
Refactor custom scripts to not be stored in one giant vector of lines
This commit refactors custom level scripts to no longer be stored in one
giant vector containing not only every single script name, but every
single script's contents as well. More specifically,
scriptclass::customscript has been converted to an std::vector<Script>
scriptclass::customscripts (note the extra S), and a Script is just a
struct with an std::string name and std::vector<std::string> contents.
This is an improvement in both performance and maintainability. The game
no longer has to look through script contents in case they're actually
script names, and then manually extract the script contents from there.
Instead, all it has to do is look for script names only. And the
contents are provided for free. This results in a performance gain.
Also, the old system resulted in lots of boilerplate everywhere anytime
scripts had to be handled or parsed. Now, the boilerplate is only done
when saving or loading a custom level. This makes code quality much,
much better.
To be sure I didn't actually change anything, I tested by first saving
Dimension Open in current 2.3 (because current 2.3 gets rid of the
awful edentity whitespace), and then resaved it on this patch. There is
absolutely no difference between the current-2.3-resave and
this-patch-resave.
2020-06-12 02:31:57 +02:00
|
|
|
for(size_t i = 0; i < script.customscripts.size(); i++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
Refactor custom scripts to not be stored in one giant vector of lines
This commit refactors custom level scripts to no longer be stored in one
giant vector containing not only every single script name, but every
single script's contents as well. More specifically,
scriptclass::customscript has been converted to an std::vector<Script>
scriptclass::customscripts (note the extra S), and a Script is just a
struct with an std::string name and std::vector<std::string> contents.
This is an improvement in both performance and maintainability. The game
no longer has to look through script contents in case they're actually
script names, and then manually extract the script contents from there.
Instead, all it has to do is look for script names only. And the
contents are provided for free. This results in a performance gain.
Also, the old system resulted in lots of boilerplate everywhere anytime
scripts had to be handled or parsed. Now, the boilerplate is only done
when saving or loading a custom level. This makes code quality much,
much better.
To be sure I didn't actually change anything, I tested by first saving
Dimension Open in current 2.3 (because current 2.3 gets rid of the
awful edentity whitespace), and then resaved it on this patch. There is
absolutely no difference between the current-2.3-resave and
this-patch-resave.
2020-06-12 02:31:57 +02:00
|
|
|
Script& script_ = script.customscripts[i];
|
|
|
|
|
|
|
|
scriptString += script_.name + ":|";
|
2021-02-16 06:11:08 +01:00
|
|
|
for (size_t ii = 0; ii < script_.contents.size(); ++ii)
|
Refactor custom scripts to not be stored in one giant vector of lines
This commit refactors custom level scripts to no longer be stored in one
giant vector containing not only every single script name, but every
single script's contents as well. More specifically,
scriptclass::customscript has been converted to an std::vector<Script>
scriptclass::customscripts (note the extra S), and a Script is just a
struct with an std::string name and std::vector<std::string> contents.
This is an improvement in both performance and maintainability. The game
no longer has to look through script contents in case they're actually
script names, and then manually extract the script contents from there.
Instead, all it has to do is look for script names only. And the
contents are provided for free. This results in a performance gain.
Also, the old system resulted in lots of boilerplate everywhere anytime
scripts had to be handled or parsed. Now, the boilerplate is only done
when saving or loading a custom level. This makes code quality much,
much better.
To be sure I didn't actually change anything, I tested by first saving
Dimension Open in current 2.3 (because current 2.3 gets rid of the
awful edentity whitespace), and then resaved it on this patch. There is
absolutely no difference between the current-2.3-resave and
this-patch-resave.
2020-06-12 02:31:57 +02:00
|
|
|
{
|
Fix variables shadowing other variables
In C++, when you have two variables in different scopes with the same
name, the inner scope wins. Except you have to be really careful because
sometimes they're not (#507). So it's better to just always have unique
variable names and make sure to never clash a name with a variable in an
outer scope - after all, the C++ compiler and standard might be fine
with it, but that doesn't mean humans can't make mistakes reading or
writing it.
Usually I just renamed the inner variables, but for tx/ty in editor.cpp,
I just got rid of the ridiculous overcomplicated modulo calculations and
replaced them with actual simple modulo calculations, because the
existing ones were just ridiculous. Actually, somebody ought to find
every instance of the overcomplicated modulos and replace them with the
actual good ones, because it's really stupid, quite frankly...
2020-11-04 09:37:46 +01:00
|
|
|
scriptString += script_.contents[ii];
|
2020-09-15 02:34:12 +02:00
|
|
|
|
|
|
|
// Inserts a space if the line ends with a :
|
Fix variables shadowing other variables
In C++, when you have two variables in different scopes with the same
name, the inner scope wins. Except you have to be really careful because
sometimes they're not (#507). So it's better to just always have unique
variable names and make sure to never clash a name with a variable in an
outer scope - after all, the C++ compiler and standard might be fine
with it, but that doesn't mean humans can't make mistakes reading or
writing it.
Usually I just renamed the inner variables, but for tx/ty in editor.cpp,
I just got rid of the ridiculous overcomplicated modulo calculations and
replaced them with actual simple modulo calculations, because the
existing ones were just ridiculous. Actually, somebody ought to find
every instance of the overcomplicated modulos and replace them with the
actual good ones, because it's really stupid, quite frankly...
2020-11-04 09:37:46 +01:00
|
|
|
if (script_.contents[ii].length() && *script_.contents[ii].rbegin() == ':')
|
2020-09-15 02:34:12 +02:00
|
|
|
{
|
|
|
|
scriptString += " ";
|
|
|
|
}
|
|
|
|
|
|
|
|
scriptString += "|";
|
Refactor custom scripts to not be stored in one giant vector of lines
This commit refactors custom level scripts to no longer be stored in one
giant vector containing not only every single script name, but every
single script's contents as well. More specifically,
scriptclass::customscript has been converted to an std::vector<Script>
scriptclass::customscripts (note the extra S), and a Script is just a
struct with an std::string name and std::vector<std::string> contents.
This is an improvement in both performance and maintainability. The game
no longer has to look through script contents in case they're actually
script names, and then manually extract the script contents from there.
Instead, all it has to do is look for script names only. And the
contents are provided for free. This results in a performance gain.
Also, the old system resulted in lots of boilerplate everywhere anytime
scripts had to be handled or parsed. Now, the boilerplate is only done
when saving or loading a custom level. This makes code quality much,
much better.
To be sure I didn't actually change anything, I tested by first saving
Dimension Open in current 2.3 (because current 2.3 gets rid of the
awful edentity whitespace), and then resaved it on this patch. There is
absolutely no difference between the current-2.3-resave and
this-patch-resave.
2020-06-12 02:31:57 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-09-25 18:31:03 +02:00
|
|
|
xml::update_tag(data, "script", scriptString.c_str());
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-11-03 18:55:26 +01:00
|
|
|
return FILESYSTEM_saveTiXml2Document(newpath.c_str(), doc);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-01-10 18:14:37 +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
|
|
|
{
|
2020-03-01 21:24:43 +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
|
|
|
}
|
|
|
|
|
2021-01-10 18:14:37 +01:00
|
|
|
static void removeedentity( int t )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-01 21:24:43 +01:00
|
|
|
edentity.erase(edentity.begin() + t);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2021-01-10 18:14:37 +01:00
|
|
|
static int edentat( int xp, int yp )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-01 21:24:43 +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;
|
|
|
|
}
|
|
|
|
|
2021-01-10 18:14:37 +01:00
|
|
|
static void fillbox( int x, int y, int x2, int y2, int c )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 21:52:10 +02: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
|
|
|
}
|
|
|
|
|
2021-01-10 18:14:37 +01:00
|
|
|
static void fillboxabs( int x, int y, int x2, int y2, int c )
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 21:52:10 +02: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
|
|
|
}
|
|
|
|
|
|
|
|
|
Explicitly declare void for all void parameter functions (#628)
Apparently in C, if you have `void test();`, it's completely okay to do
`test(2);`. The function will take in the argument, but just discard it
and throw it away. It's like a trash can, and a rude one at that. If you
declare it like `void test(void);`, this is prevented.
This is not a problem in C++ - doing `void test();` and `test(2);` is
guaranteed to result in a compile error (this also means that right now,
at least in all `.cpp` files, nobody is ever calling a void parameter
function with arguments and having their arguments be thrown away).
However, we may not be using C++ in the future, so I just want to lay
down the precedent that if a function takes in no arguments, you must
explicitly declare it as such.
I would've added `-Wstrict-prototypes`, but it produces an annoying
warning message saying it doesn't work in C++ mode if you're compiling
in C++ mode. So it can be added later.
2021-02-25 23:23:59 +01:00
|
|
|
void editorclass::generatecustomminimap(void)
|
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);
|
|
|
|
}
|
|
|
|
|
2021-02-26 00:37:03 +01:00
|
|
|
ClearSurface(graphics.images[12]);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
int tm=0;
|
|
|
|
int temp=0;
|
|
|
|
//Scan over the map size
|
2020-09-28 04:15:06 +02:00
|
|
|
if(mapheight<=5 && mapwidth<=5)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//4x map
|
2020-09-28 04:15:06 +02:00
|
|
|
for(int j2=0; j2<mapheight; j2++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-09-28 04:15:06 +02:00
|
|
|
for(int i2=0; i2<mapwidth; i2++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//Ok, now scan over each square
|
|
|
|
tm=196;
|
2020-09-28 04:15:06 +02:00
|
|
|
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++)
|
|
|
|
{
|
2020-09-28 04:15:06 +02:00
|
|
|
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
|
2020-03-31 21:52:10 +02:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-09-28 04:15:06 +02:00
|
|
|
else if(mapheight<=10 && mapwidth<=10)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//2x map
|
2020-09-28 04:15:06 +02:00
|
|
|
for(int j2=0; j2<mapheight; j2++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-09-28 04:15:06 +02:00
|
|
|
for(int i2=0; i2<mapwidth; i2++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//Ok, now scan over each square
|
|
|
|
tm=196;
|
2020-09-28 04:15:06 +02:00
|
|
|
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++)
|
|
|
|
{
|
2020-09-28 04:15:06 +02:00
|
|
|
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
|
2020-03-31 21:52:10 +02:00
|
|
|
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
|
|
|
|
{
|
2020-09-28 04:15:06 +02:00
|
|
|
for(int j2=0; j2<mapheight; j2++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-09-28 04:15:06 +02:00
|
|
|
for(int i2=0; i2<mapwidth; i2++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//Ok, now scan over each square
|
|
|
|
tm=196;
|
2020-09-28 04:15:06 +02:00
|
|
|
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++)
|
|
|
|
{
|
2020-09-28 04:15:06 +02:00
|
|
|
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
|
2020-03-31 21:52:10 +02:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-20 00:23:28 +02:00
|
|
|
#if !defined(NO_EDITOR)
|
2021-01-10 18:14:37 +01:00
|
|
|
static void editormenurender(int tr, int tg, int tb)
|
2020-04-16 05:55:34 +02:00
|
|
|
{
|
2020-09-28 04:15:06 +02:00
|
|
|
extern editorclass ed;
|
2020-04-17 00:19:17 +02:00
|
|
|
switch (game.currentmenuname)
|
2020-04-16 05:55:34 +02:00
|
|
|
{
|
2020-04-17 00:19:17 +02:00
|
|
|
case Menu::ed_settings:
|
2020-04-16 05:55:34 +02:00
|
|
|
graphics.bigprint( -1, 75, "Map Settings", tr, tg, tb, true);
|
Add a player trail to the editor (ghosts)
A few months ago, I added ghosts to the VVVVVV: Community Edition editor. I was told recently I should think
about upstreaming it, and with Terry saying go ahead I finally ported them into VVVVVV. There's one slight
difference however--you can choose whether you have them or not in the editor's settings menu. They're off by
default, and this is saved to the save file.
Anyway, when you're playtesting, the game saves the players position, color, room coordinates and sprite every 3
frames. The max is 100, where if it tries to add more, the oldest one gets removed.
When you exit playtesting, the saved positions appear one at a time, and you can use the Z key to speed it up.
[Here's a video of them in action.](https://o.lol-sa.me/4H21zCv.mp4)
2020-06-13 00:04:35 +02:00
|
|
|
if (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);
|
|
|
|
}
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::ed_desc:
|
2020-04-16 05:55:34 +02:00
|
|
|
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);
|
|
|
|
}
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::ed_music:
|
2020-07-01 03:40:18 +02:00
|
|
|
{
|
2020-04-16 05:55:34 +02:00
|
|
|
graphics.bigprint( -1, 65, "Map Music", tr, tg, tb, true);
|
|
|
|
|
|
|
|
graphics.Print( -1, 85, "Current map music:", tr, tg, tb, true);
|
2020-07-01 03:40:18 +02:00
|
|
|
std::string songname;
|
2020-04-16 05:55:34 +02:00
|
|
|
switch(ed.levmusic)
|
|
|
|
{
|
|
|
|
case 0:
|
2020-07-01 03:40:18 +02:00
|
|
|
songname = "No background music";
|
2020-04-16 05:55:34 +02:00
|
|
|
break;
|
|
|
|
case 1:
|
2020-07-01 03:40:18 +02:00
|
|
|
songname = "1: Pushing Onwards";
|
2020-04-16 05:55:34 +02:00
|
|
|
break;
|
|
|
|
case 2:
|
2020-07-01 03:40:18 +02:00
|
|
|
songname = "2: Positive Force";
|
2020-04-16 05:55:34 +02:00
|
|
|
break;
|
|
|
|
case 3:
|
2020-07-01 03:40:18 +02:00
|
|
|
songname = "3: Potential for Anything";
|
2020-04-16 05:55:34 +02:00
|
|
|
break;
|
|
|
|
case 4:
|
2020-07-01 03:40:18 +02:00
|
|
|
songname = "4: Passion for Exploring";
|
2020-04-16 05:55:34 +02:00
|
|
|
break;
|
2020-07-01 03:41:20 +02:00
|
|
|
case 5:
|
|
|
|
songname = "N/A: Pause";
|
|
|
|
break;
|
2020-04-16 05:55:34 +02:00
|
|
|
case 6:
|
2020-07-01 03:40:18 +02:00
|
|
|
songname = "5: Presenting VVVVVV";
|
2020-04-16 05:55:34 +02:00
|
|
|
break;
|
2020-07-01 03:41:20 +02:00
|
|
|
case 7:
|
|
|
|
songname = "N/A: Plenary";
|
|
|
|
break;
|
2020-04-16 05:55:34 +02:00
|
|
|
case 8:
|
2020-07-01 03:40:18 +02:00
|
|
|
songname = "6: Predestined Fate";
|
2020-04-16 05:55:34 +02:00
|
|
|
break;
|
2020-07-01 03:41:20 +02:00
|
|
|
case 9:
|
|
|
|
songname = "N/A: ecroF evitisoP";
|
|
|
|
break;
|
2020-04-16 05:55:34 +02:00
|
|
|
case 10:
|
2020-07-01 03:40:18 +02:00
|
|
|
songname = "7: Popular Potpourri";
|
2020-04-16 05:55:34 +02:00
|
|
|
break;
|
|
|
|
case 11:
|
2020-07-01 03:40:18 +02:00
|
|
|
songname = "8: Pipe Dream";
|
2020-04-16 05:55:34 +02:00
|
|
|
break;
|
|
|
|
case 12:
|
2020-07-01 03:40:18 +02:00
|
|
|
songname = "9: Pressure Cooker";
|
2020-04-16 05:55:34 +02:00
|
|
|
break;
|
|
|
|
case 13:
|
2020-07-01 03:40:18 +02:00
|
|
|
songname = "10: Paced Energy";
|
2020-04-16 05:55:34 +02:00
|
|
|
break;
|
|
|
|
case 14:
|
2020-07-01 03:40:18 +02:00
|
|
|
songname = "11: Piercing the Sky";
|
2020-04-16 05:55:34 +02:00
|
|
|
break;
|
2020-07-01 03:41:20 +02:00
|
|
|
case 15:
|
|
|
|
songname = "N/A: Predestined Fate Remix";
|
|
|
|
break;
|
2020-04-16 05:55:34 +02:00
|
|
|
default:
|
2020-07-01 03:40:18 +02:00
|
|
|
songname = "?: something else";
|
2020-04-16 05:55:34 +02:00
|
|
|
break;
|
|
|
|
}
|
2020-07-01 03:40:18 +02:00
|
|
|
graphics.Print( -1, 120, songname, tr, tg, tb, true);
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
2020-07-01 03:40:18 +02:00
|
|
|
}
|
2020-04-17 00:19:17 +02:00
|
|
|
case Menu::ed_quit:
|
2020-04-16 05:55:34 +02:00
|
|
|
graphics.bigprint( -1, 90, "Save before", tr, tg, tb, true);
|
|
|
|
graphics.bigprint( -1, 110, "quitting?", tr, tg, tb, true);
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
2020-04-16 05:55:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Explicitly declare void for all void parameter functions (#628)
Apparently in C, if you have `void test();`, it's completely okay to do
`test(2);`. The function will take in the argument, but just discard it
and throw it away. It's like a trash can, and a rude one at that. If you
declare it like `void test(void);`, this is prevented.
This is not a problem in C++ - doing `void test();` and `test(2);` is
guaranteed to result in a compile error (this also means that right now,
at least in all `.cpp` files, nobody is ever calling a void parameter
function with arguments and having their arguments be thrown away).
However, we may not be using C++ in the future, so I just want to lay
down the precedent that if a function takes in no arguments, you must
explicitly declare it as such.
I would've added `-Wstrict-prototypes`, but it produces an annoying
warning message saying it doesn't work in C++ mode if you're compiling
in C++ mode. So it can be added later.
2021-02-25 23:23:59 +01:00
|
|
|
void editorrender(void)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-09-28 04:15:06 +02:00
|
|
|
extern editorclass ed;
|
2020-05-09 23:44:51 +02:00
|
|
|
if (game.shouldreturntoeditor)
|
|
|
|
{
|
|
|
|
graphics.backgrounddrawn = false;
|
|
|
|
}
|
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
//Draw grid
|
|
|
|
|
2021-02-26 00:37:03 +01:00
|
|
|
ClearSurface(graphics.backBuffer);
|
2020-01-01 21:29:24 +01:00
|
|
|
for(int j=0; j<30; j++)
|
|
|
|
{
|
|
|
|
for(int i=0; i<40; i++)
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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
|
2020-03-31 21:52:10 +02:00
|
|
|
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
|
2020-03-31 21:52:10 +02:00
|
|
|
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:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.rcol=ed.getwarpbackground(ed.levx, ed.levy);
|
|
|
|
graphics.drawbackground(3);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 2:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.rcol=ed.getwarpbackground(ed.levx, ed.levy);
|
|
|
|
graphics.drawbackground(4);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3:
|
2020-03-31 21:52:10 +02:00
|
|
|
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)]];
|
2020-04-02 00:42:22 +02:00
|
|
|
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)]];
|
2020-04-02 00:47:35 +02:00
|
|
|
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)
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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)
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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)
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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)
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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;
|
|
|
|
|
2020-07-03 04:28:35 +02:00
|
|
|
int temp2=edentat(ed.tilex+ (ed.levx*40),ed.tiley+ (ed.levy*30));
|
2020-01-27 02:15:49 +01:00
|
|
|
|
2020-09-25 19:35:03 +02:00
|
|
|
// 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;
|
|
|
|
|
2020-01-27 02:15:49 +01:00
|
|
|
// Draw entities backward to remain accurate with ingame
|
2020-03-01 21:24:43 +01:00
|
|
|
for (int i = edentity.size() - 1; i >= 0; i--)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
point tpoint;
|
|
|
|
SDL_Rect drawRect;
|
|
|
|
|
Fix variables shadowing other variables
In C++, when you have two variables in different scopes with the same
name, the inner scope wins. Except you have to be really careful because
sometimes they're not (#507). So it's better to just always have unique
variable names and make sure to never clash a name with a variable in an
outer scope - after all, the C++ compiler and standard might be fine
with it, but that doesn't mean humans can't make mistakes reading or
writing it.
Usually I just renamed the inner variables, but for tx/ty in editor.cpp,
I just got rid of the ridiculous overcomplicated modulo calculations and
replaced them with actual simple modulo calculations, because the
existing ones were just ridiculous. Actually, somebody ought to find
every instance of the overcomplicated modulos and replace them with the
actual good ones, because it's really stupid, quite frankly...
2020-11-04 09:37:46 +01:00
|
|
|
//if() on screen
|
2020-11-08 02:12:54 +01:00
|
|
|
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
|
2020-09-25 19:35:03 +02:00
|
|
|
if (custom_gray) {
|
|
|
|
graphics.setcol(18);
|
|
|
|
ed.entcolreal = graphics.ct.colour;
|
|
|
|
}
|
2020-05-02 22:53:19 +02:00
|
|
|
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);
|
2020-03-31 21:52:10 +02:00
|
|
|
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
|
2020-09-16 00:21:34 +02:00
|
|
|
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);
|
2020-03-31 21:52:10 +02:00
|
|
|
drawRect = graphics.tiles_rect;
|
2020-01-01 21:29:24 +01:00
|
|
|
drawRect.x += tpoint.x;
|
|
|
|
drawRect.y += tpoint.y;
|
2020-10-11 22:56:17 +02:00
|
|
|
for (int j = 0; j < 4; j++) {
|
2020-09-25 19:35:03 +02:00
|
|
|
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)
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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)
|
|
|
|
{
|
2020-03-31 21:52:10 +02: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
|
|
|
}
|
|
|
|
else if(edentity[i].p1==6)
|
|
|
|
{
|
2020-03-31 21:52:10 +02: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
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2020-03-31 21:52:10 +02:00
|
|
|
drawRect = graphics.tiles_rect;
|
2020-01-01 21:29:24 +01:00
|
|
|
drawRect.x += tpoint.x;
|
|
|
|
drawRect.y += tpoint.y;
|
2020-10-11 22:56:17 +02:00
|
|
|
for (int j = 0; j < 4; j++) {
|
2020-09-25 19:35:03 +02:00
|
|
|
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)
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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)
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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
|
2020-09-16 00:21:34 +02:00
|
|
|
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);
|
2020-03-31 21:52:10 +02:00
|
|
|
drawRect = graphics.tiles_rect;
|
2020-01-01 21:29:24 +01:00
|
|
|
drawRect.x += tpoint.x;
|
|
|
|
drawRect.y += tpoint.y;
|
2020-10-11 22:56:17 +02:00
|
|
|
for (int j = 0; j < 4; j++) {
|
2020-09-25 19:35:03 +02:00
|
|
|
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
|
|
|
|
2020-03-31 21:52:10 +02: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
|
2020-03-31 21:52:10 +02:00
|
|
|
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
|
Improve support for retextured checkpoints and terminals in the editor
Retextured checkpoints have always been in the game, but clicking on
them in the editor would lead to them losing their retextured-ness. So,
checkpoints should be left alone if their p1 isn't either 0 or 1. Also,
they don't show up properly in the editor, so that's fixed, too.
Retextured and flipped terminals were added in 2.3, and show up properly
in-game, but don't properly show up in the editor, either. So now they
show up in the editor. Additionally, clicking on them will flip the
terminal as well, but only if its p1 is 0 or 1, just like checkpoints
now do.
2021-01-12 02:19:20 +01:00
|
|
|
graphics.drawsprite((edentity[i].x*8)- (ed.levx*40*8),(edentity[i].y*8)- (ed.levy*30*8),20 + edentity[i].p1,196,196,196);
|
2020-03-31 21:52:10 +02: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);
|
2020-06-30 23:42:34 +02:00
|
|
|
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;
|
|
|
|
}
|
2020-03-31 21:52:10 +02:00
|
|
|
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);
|
2020-06-30 23:42:34 +02:00
|
|
|
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;
|
|
|
|
}
|
2020-03-31 21:52:10 +02:00
|
|
|
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
|
2020-03-31 21:52:10 +02:00
|
|
|
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));
|
2020-07-03 04:28:35 +02:00
|
|
|
if(temp2==i)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 21:52:10 +02: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
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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
|
2020-05-02 22:53:19 +02:00
|
|
|
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));
|
2020-03-31 21:52:10 +02:00
|
|
|
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
|
|
|
|
{
|
2020-05-02 22:53:19 +02:00
|
|
|
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)
|
|
|
|
{
|
2020-05-02 22:53:19 +02:00
|
|
|
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
|
|
|
}
|
2020-03-31 21:52:10 +02: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)
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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)
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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
|
|
|
|
{
|
2020-01-31 19:25:37 +01:00
|
|
|
int length = utf8::unchecked::distance(edentity[i].scriptname.begin(), edentity[i].scriptname.end());
|
2020-03-31 21:52:10 +02:00
|
|
|
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
|
|
|
}
|
2020-03-31 21:52:10 +02: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
|
Improve support for retextured checkpoints and terminals in the editor
Retextured checkpoints have always been in the game, but clicking on
them in the editor would lead to them losing their retextured-ness. So,
checkpoints should be left alone if their p1 isn't either 0 or 1. Also,
they don't show up properly in the editor, so that's fixed, too.
Retextured and flipped terminals were added in 2.3, and show up properly
in-game, but don't properly show up in the editor, either. So now they
show up in the editor. Additionally, clicking on them will flip the
terminal as well, but only if its p1 is 0 or 1, just like checkpoints
now do.
2021-01-12 02:19:20 +01:00
|
|
|
{
|
|
|
|
int usethistile = edentity[i].p1;
|
|
|
|
int usethisy = (edentity[i].y % 30) * 8;
|
|
|
|
// Not a boolean: just swapping 0 and 1, leaving the rest alone
|
|
|
|
if (usethistile == 0)
|
|
|
|
{
|
|
|
|
usethistile = 1; // Unflipped
|
|
|
|
}
|
|
|
|
else if (usethistile == 1)
|
|
|
|
{
|
|
|
|
usethistile = 0; // Flipped;
|
|
|
|
usethisy -= 8;
|
|
|
|
}
|
|
|
|
graphics.drawsprite((edentity[i].x*8)- (ed.levx*40*8), usethisy + 8, usethistile + 16, 96,96,96);
|
2020-03-31 21:52:10 +02:00
|
|
|
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-07-03 04:28:35 +02:00
|
|
|
if(temp2==i)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 21:52:10 +02: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;
|
Improve support for retextured checkpoints and terminals in the editor
Retextured checkpoints have always been in the game, but clicking on
them in the editor would lead to them losing their retextured-ness. So,
checkpoints should be left alone if their p1 isn't either 0 or 1. Also,
they don't show up properly in the editor, so that's fixed, too.
Retextured and flipped terminals were added in 2.3, and show up properly
in-game, but don't properly show up in the editor, either. So now they
show up in the editor. Additionally, clicking on them will flip the
terminal as well, but only if its p1 is 0 or 1, just like checkpoints
now do.
2021-01-12 02:19:20 +01:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
case 19: //Script Triggers
|
2020-03-31 21:52:10 +02:00
|
|
|
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));
|
2020-07-03 04:28:35 +02:00
|
|
|
if(temp2==i)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 21:52:10 +02: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);
|
2020-06-30 23:42:34 +02:00
|
|
|
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;
|
|
|
|
}
|
2020-03-31 21:52:10 +02:00
|
|
|
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);
|
2020-06-30 23:42:34 +02:00
|
|
|
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;
|
|
|
|
}
|
2020-03-31 21:52:10 +02:00
|
|
|
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)
|
|
|
|
{
|
2020-11-08 02:12:54 +01:00
|
|
|
if (edentity[i].p1 / 40 == ed.levx && edentity[i].p2 / 30 == ed.levy)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 21:52:10 +02: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)
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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)
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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)
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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)
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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,
|
2020-03-31 21:52:10 +02:00
|
|
|
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)
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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,
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.getBGR(64,64,255-(help.glow/2)));
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Add a player trail to the editor (ghosts)
A few months ago, I added ghosts to the VVVVVV: Community Edition editor. I was told recently I should think
about upstreaming it, and with Terry saying go ahead I finally ported them into VVVVVV. There's one slight
difference however--you can choose whether you have them or not in the editor's settings menu. They're off by
default, and this is saved to the save file.
Anyway, when you're playtesting, the game saves the players position, color, room coordinates and sprite every 3
frames. The max is 100, where if it tries to add more, the oldest one gets removed.
When you exit playtesting, the saved positions appear one at a time, and you can use the Z key to speed it up.
[Here's a video of them in action.](https://o.lol-sa.me/4H21zCv.mp4)
2020-06-13 00:04:35 +02:00
|
|
|
//Draw ghosts (spooky!)
|
|
|
|
if (game.ghostsenabled) {
|
2021-02-26 00:37:03 +01:00
|
|
|
ClearSurface(graphics.ghostbuffer);
|
Add a player trail to the editor (ghosts)
A few months ago, I added ghosts to the VVVVVV: Community Edition editor. I was told recently I should think
about upstreaming it, and with Terry saying go ahead I finally ported them into VVVVVV. There's one slight
difference however--you can choose whether you have them or not in the editor's settings menu. They're off by
default, and this is saved to the save file.
Anyway, when you're playtesting, the game saves the players position, color, room coordinates and sprite every 3
frames. The max is 100, where if it tries to add more, the oldest one gets removed.
When you exit playtesting, the saved positions appear one at a time, and you can use the Z key to speed it up.
[Here's a video of them in action.](https://o.lol-sa.me/4H21zCv.mp4)
2020-06-13 00:04:35 +02:00
|
|
|
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 :)
|
2020-06-14 03:35:12 +02:00
|
|
|
if (ed.ghosts[i].rx != ed.levx || ed.ghosts[i].ry != ed.levy
|
2020-09-08 09:31:44 +02:00
|
|
|
|| !INBOUNDS_VEC(ed.ghosts[i].frame, graphics.sprites))
|
Add a player trail to the editor (ghosts)
A few months ago, I added ghosts to the VVVVVV: Community Edition editor. I was told recently I should think
about upstreaming it, and with Terry saying go ahead I finally ported them into VVVVVV. There's one slight
difference however--you can choose whether you have them or not in the editor's settings menu. They're off by
default, and this is saved to the save file.
Anyway, when you're playtesting, the game saves the players position, color, room coordinates and sprite every 3
frames. The max is 100, where if it tries to add more, the oldest one gets removed.
When you exit playtesting, the saved positions appear one at a time, and you can use the Z key to speed it up.
[Here's a video of them in action.](https://o.lol-sa.me/4H21zCv.mp4)
2020-06-13 00:04:35 +02:00
|
|
|
continue;
|
|
|
|
point tpoint;
|
|
|
|
tpoint.x = ed.ghosts[i].x;
|
|
|
|
tpoint.y = ed.ghosts[i].y;
|
2020-06-13 02:34:19 +02:00
|
|
|
graphics.setcolreal(ed.ghosts[i].realcol);
|
Add a player trail to the editor (ghosts)
A few months ago, I added ghosts to the VVVVVV: Community Edition editor. I was told recently I should think
about upstreaming it, and with Terry saying go ahead I finally ported them into VVVVVV. There's one slight
difference however--you can choose whether you have them or not in the editor's settings menu. They're off by
default, and this is saved to the save file.
Anyway, when you're playtesting, the game saves the players position, color, room coordinates and sprite every 3
frames. The max is 100, where if it tries to add more, the oldest one gets removed.
When you exit playtesting, the saved positions appear one at a time, and you can use the Z key to speed it up.
[Here's a video of them in action.](https://o.lol-sa.me/4H21zCv.mp4)
2020-06-13 00:04:35 +02:00
|
|
|
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
|
2020-03-31 21:52:10 +02:00
|
|
|
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
|
2020-03-31 21:52:10 +02:00
|
|
|
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
|
2020-03-31 21:52:10 +02:00
|
|
|
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)
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
fillboxabs((ed.tilex*8),(ed.tiley*8),8,8, graphics.getRGB(200,32,32));
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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
|
2020-03-31 21:52:10 +02:00
|
|
|
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)
|
|
|
|
{
|
2020-08-01 21:23:56 +02:00
|
|
|
if(ed.bmod && ed.drawmode<2)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-08-01 21:23:56 +02:00
|
|
|
fillboxabs((ed.tilex*8),0,8,240,graphics.getRGB(200,32,32));
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-08-01 21:23:56 +02:00
|
|
|
else if(ed.hmod && ed.drawmode<2)
|
2020-06-17 23:49:57 +02:00
|
|
|
{
|
2020-08-01 21:23:56 +02:00
|
|
|
fillboxabs(0,(ed.tiley*8),320,8,graphics.getRGB(200,32,32));
|
2020-06-17 23:49:57 +02:00
|
|
|
}
|
|
|
|
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));
|
|
|
|
}
|
2020-08-01 21:23:56 +02:00
|
|
|
else if(ed.cmod && ed.drawmode<2)
|
2020-06-17 23:49:57 +02:00
|
|
|
{
|
2020-08-01 21:23:56 +02:00
|
|
|
fillboxabs((ed.tilex*8)-24,(ed.tiley*8)-24,24+32,24+32, graphics.getRGB(200,32,32));
|
2020-06-17 23:49:57 +02:00
|
|
|
}
|
2020-08-01 21:23:56 +02:00
|
|
|
else if(ed.xmod && ed.drawmode<2)
|
2020-06-17 23:49:57 +02:00
|
|
|
{
|
2020-08-01 21:23:56 +02:00
|
|
|
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-06-17 23:49:57 +02:00
|
|
|
}
|
2020-08-01 21:23:56 +02:00
|
|
|
|
|
|
|
|
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;
|
2020-03-31 21:52:10 +02:00
|
|
|
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++)
|
|
|
|
{
|
2020-04-02 00:42:22 +02:00
|
|
|
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++)
|
|
|
|
{
|
2020-04-02 00:47:35 +02:00
|
|
|
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
|
2020-08-01 22:27:03 +02:00
|
|
|
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)
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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)
|
|
|
|
{
|
2020-04-02 00:42:22 +02:00
|
|
|
graphics.drawtile(45,45-t2,ed.dmtile);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-04-02 00:47:35 +02:00
|
|
|
graphics.drawtile2(45,45-t2,ed.dmtile);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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)
|
|
|
|
{
|
2020-04-02 00:42:22 +02:00
|
|
|
graphics.drawtile(45,12,ed.dmtile);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-04-02 00:47:35 +02:00
|
|
|
graphics.drawtile2(45,12,ed.dmtile);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Draw GUI
|
|
|
|
if(ed.boundarymod>0)
|
|
|
|
{
|
|
|
|
if(ed.boundarymod==1)
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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:
|
2020-03-31 21:52:10 +02:00
|
|
|
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:
|
2020-03-31 21:52:10 +02:00
|
|
|
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:
|
2020-03-31 21:52:10 +02:00
|
|
|
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:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.Print(4, 232, "COPY TILES: Click on top left", 255,255,255, false);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
default:
|
2020-03-31 21:52:10 +02:00
|
|
|
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)
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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:
|
2020-03-31 21:52:10 +02:00
|
|
|
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:
|
2020-03-31 21:52:10 +02:00
|
|
|
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:
|
2020-03-31 21:52:10 +02:00
|
|
|
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:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.Print(4, 232, "COPY TILES: Click on bottom right", 255,255,255, false);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
default:
|
2020-03-31 21:52:10 +02:00
|
|
|
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!
|
2020-03-31 21:52:10 +02:00
|
|
|
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:
|
2020-03-31 21:52:10 +02:00
|
|
|
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)] + " <";
|
2020-07-03 23:54:23 +02:00
|
|
|
for (size_t ii = 0; ii < tstring.length(); ii++)
|
|
|
|
{
|
|
|
|
tstring[ii] = SDL_toupper(tstring[ii]);
|
|
|
|
}
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.Print(16,68+(i*16),tstring,123, 111, 218, true);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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
|
2020-03-31 21:52:10 +02:00
|
|
|
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
|
|
|
{
|
2020-03-31 21:52:10 +02: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)
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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)
|
|
|
|
{
|
2020-04-29 20:15:23 +02:00
|
|
|
if(!game.colourblindmode)
|
|
|
|
{
|
2020-11-03 00:23:53 +01:00
|
|
|
graphics.drawtowerbackground(graphics.titlebg);
|
2020-04-29 20:15:23 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-02-26 00:37:03 +01:00
|
|
|
ClearSurface(graphics.backBuffer);
|
2020-04-29 20:15:23 +02:00
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2021-01-08 02:37:38 +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;
|
2020-04-16 05:55:34 +02:00
|
|
|
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
|
|
|
}
|
2020-07-01 08:17:26 +02:00
|
|
|
else if (ed.textmod)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-07-01 08:17:26 +02: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
|
|
|
{
|
2020-07-01 08:17:26 +02:00
|
|
|
input += "_";
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-07-01 08:17:26 +02:00
|
|
|
input += " ";
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-07-01 08:17:26 +02: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
|
2020-03-31 21:52:10 +02: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, "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)
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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++)
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
FillRect(graphics.backBuffer, 4+(i*tg), 209,20,20,graphics.getRGB(32,32,32));
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 21:52:10 +02: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:
|
2020-04-02 00:42:22 +02:00
|
|
|
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;
|
2020-04-02 00:42:22 +02:00
|
|
|
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;
|
2020-04-02 00:42:22 +02:00
|
|
|
graphics.drawtile(tx+4,ty+4,8);
|
2020-01-01 21:29:24 +01:00
|
|
|
//3:
|
|
|
|
tx+=tg;
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.drawsprite(tx,ty,22,196,196,196);
|
2020-01-01 21:29:24 +01:00
|
|
|
//4:
|
|
|
|
tx+=tg;
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.drawsprite(tx,ty,21,196,196,196);
|
2020-01-01 21:29:24 +01:00
|
|
|
//5:
|
|
|
|
tx+=tg;
|
2020-04-02 00:42:22 +02:00
|
|
|
graphics.drawtile(tx,ty+4,3);
|
|
|
|
graphics.drawtile(tx+8,ty+4,4);
|
2020-01-01 21:29:24 +01:00
|
|
|
//6:
|
|
|
|
tx+=tg;
|
2020-04-02 00:42:22 +02:00
|
|
|
graphics.drawtile(tx,ty+4,24);
|
|
|
|
graphics.drawtile(tx+8,ty+4,24);
|
2020-01-01 21:29:24 +01:00
|
|
|
//7:
|
|
|
|
tx+=tg;
|
2020-04-02 00:42:22 +02:00
|
|
|
graphics.drawtile(tx,ty+4,1);
|
|
|
|
graphics.drawtile(tx+8,ty+4,1);
|
2020-01-01 21:29:24 +01:00
|
|
|
//8:
|
|
|
|
tx+=tg;
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.drawsprite(tx,ty,78+ed.entframe,196,196,196);
|
2020-01-01 21:29:24 +01:00
|
|
|
//9:
|
|
|
|
tx+=tg;
|
2020-03-31 21:52:10 +02:00
|
|
|
FillRect(graphics.backBuffer, tx+2,ty+8,12,1,graphics.getRGB(255,255,255));
|
2020-01-01 21:29:24 +01:00
|
|
|
|
Fix editor tool menu inconsistencies
This patch de-duplicates the tool drawing code a bit in the menu that
gets brought up when you press Space in the level editor, as well as
fixes several bugs related to the fact that the original author(s) of
the code decided to copy-paste everything. (It was most likely Terry,
judging by the distinct lack of whitespace between tokens in the code.)
There are two "pages" of tools that get shown when you open the tool
menu, according to your currently-selected tool.
1. On the first page, your currently-selected tool gets a brighter
outline. However, on the second page, the code to draw the outline over
your currently-selected tool is missing. So I've fixed that.
2. On the first page, the glyph indicator next to the tool icon also
gets brighter when you have that tool selected. However, on the
second page, the code that drew the brighter-colored indicator got
ran before the code that drew the normal-colored indicator, so this
was never shown. This is also fixed.
3. The glyph indicator of the gravity line tool didn't get brighter when
you had it selected, due to its special-cased copy-pasted code
drawing its brighter color before drawing its normal color. This has
also been fixed.
4. Lastly, the tool menu no longer draws the brighter-colored glyphs on
top of the normal-colored glyphs. Instead, the menu will simply draw
the brighter-colored glyphs and will not draw the normal-colored
glyphs in the first place. This is because double-drawing text like
this will look bad if the user has a custom font.png that has
translucent pixels, like I do.
All of these bugs have been fixed by paying off the technical debt of
copy-pasting code.
2021-01-11 06:10:03 +01:00
|
|
|
for (int i = 0; i < 10; i++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
fillboxabs(4+(i*tg), 209,20,20,graphics.getRGB(96,96,96));
|
Fix editor tool menu inconsistencies
This patch de-duplicates the tool drawing code a bit in the menu that
gets brought up when you press Space in the level editor, as well as
fixes several bugs related to the fact that the original author(s) of
the code decided to copy-paste everything. (It was most likely Terry,
judging by the distinct lack of whitespace between tokens in the code.)
There are two "pages" of tools that get shown when you open the tool
menu, according to your currently-selected tool.
1. On the first page, your currently-selected tool gets a brighter
outline. However, on the second page, the code to draw the outline over
your currently-selected tool is missing. So I've fixed that.
2. On the first page, the glyph indicator next to the tool icon also
gets brighter when you have that tool selected. However, on the
second page, the code that drew the brighter-colored indicator got
ran before the code that drew the normal-colored indicator, so this
was never shown. This is also fixed.
3. The glyph indicator of the gravity line tool didn't get brighter when
you had it selected, due to its special-cased copy-pasted code
drawing its brighter color before drawing its normal color. This has
also been fixed.
4. Lastly, the tool menu no longer draws the brighter-colored glyphs on
top of the normal-colored glyphs. Instead, the menu will simply draw
the brighter-colored glyphs and will not draw the normal-colored
glyphs in the first place. This is because double-drawing text like
this will look bad if the user has a custom font.png that has
translucent pixels, like I do.
All of these bugs have been fixed by paying off the technical debt of
copy-pasting code.
2021-01-11 06:10:03 +01:00
|
|
|
const int col = i == ed.drawmode ? 255 : 164;
|
|
|
|
const std::string glyph = i == 9 ? "0" : help.String(i + 1);
|
|
|
|
graphics.Print(22 + i*tg - 4, 225 - 4, glyph, col, col, col, false);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-03-31 21:52:10 +02:00
|
|
|
fillboxabs(4+(ed.drawmode*tg), 209,20,20,graphics.getRGB(200,200,200));
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-03-31 21:52:10 +02: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++)
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
FillRect(graphics.backBuffer, 4+(i*tg), 209,20,20,graphics.getRGB(32,32,32));
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-03-31 21:52:10 +02: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:
|
2020-03-31 21:52:10 +02:00
|
|
|
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;
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.drawsprite(tx,ty,17,196,196,196);
|
2020-01-01 21:29:24 +01:00
|
|
|
//12:
|
|
|
|
tx+=tg;
|
2020-03-31 21:52:10 +02:00
|
|
|
fillboxabs(tx+4,ty+4,8,8,graphics.getRGB(96,96,96));
|
2020-01-01 21:29:24 +01:00
|
|
|
//13:
|
|
|
|
tx+=tg;
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.drawsprite(tx,ty,18+(ed.entframe%2),196,196,196);
|
2020-01-01 21:29:24 +01:00
|
|
|
//14:
|
|
|
|
tx+=tg;
|
2020-03-31 21:52:10 +02:00
|
|
|
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;
|
2020-05-02 22:53:19 +02:00
|
|
|
graphics.drawsprite(tx,ty,186,graphics.col_crewblue);
|
2020-01-01 21:29:24 +01:00
|
|
|
//16:
|
|
|
|
tx+=tg;
|
2020-05-02 22:53:19 +02:00
|
|
|
graphics.drawsprite(tx,ty,184,graphics.col_crewcyan);
|
2020-03-31 21:52:10 +02:00
|
|
|
|
Fix editor tool menu inconsistencies
This patch de-duplicates the tool drawing code a bit in the menu that
gets brought up when you press Space in the level editor, as well as
fixes several bugs related to the fact that the original author(s) of
the code decided to copy-paste everything. (It was most likely Terry,
judging by the distinct lack of whitespace between tokens in the code.)
There are two "pages" of tools that get shown when you open the tool
menu, according to your currently-selected tool.
1. On the first page, your currently-selected tool gets a brighter
outline. However, on the second page, the code to draw the outline over
your currently-selected tool is missing. So I've fixed that.
2. On the first page, the glyph indicator next to the tool icon also
gets brighter when you have that tool selected. However, on the
second page, the code that drew the brighter-colored indicator got
ran before the code that drew the normal-colored indicator, so this
was never shown. This is also fixed.
3. The glyph indicator of the gravity line tool didn't get brighter when
you had it selected, due to its special-cased copy-pasted code
drawing its brighter color before drawing its normal color. This has
also been fixed.
4. Lastly, the tool menu no longer draws the brighter-colored glyphs on
top of the normal-colored glyphs. Instead, the menu will simply draw
the brighter-colored glyphs and will not draw the normal-colored
glyphs in the first place. This is because double-drawing text like
this will look bad if the user has a custom font.png that has
translucent pixels, like I do.
All of these bugs have been fixed by paying off the technical debt of
copy-pasting code.
2021-01-11 06:10:03 +01:00
|
|
|
for (int i = 0; i < 7; i++)
|
|
|
|
{
|
|
|
|
fillboxabs(4 + i*tg, 209, 20, 20, graphics.getRGB(96, 96, 96));
|
|
|
|
const int col = i + 10 == ed.drawmode ? 255 : 164;
|
|
|
|
static const char glyphs[] = "RTYUIOP";
|
|
|
|
graphics.Print(22 + i*tg - 4, 225 - 4, std::string(1, glyphs[i]), col, col, col, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
fillboxabs(4 + (ed.drawmode - 10) * tg, 209, 20, 20, graphics.getRGB(200, 200, 200));
|
2020-03-31 21:52:10 +02:00
|
|
|
|
|
|
|
graphics.Print(4, 232, "2/2", 196, 196, 255 - help.glow, false);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.Print(128, 232, "< and > keys change tool", 196, 196, 255 - help.glow, false);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-03-31 21:52:10 +02: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:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,199, "1: Walls",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 1:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,199, "2: Backing",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 2:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,199, "3: Spikes",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,199, "4: Trinkets",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 4:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,199, "5: Checkpoint",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 5:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,199, "6: Disappear",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 6:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,199, "7: Conveyors",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 7:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,199, "8: Moving",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 8:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,199, "9: Enemies",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 9:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,199, "0: Grav Line",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 10:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,199, "R: Roomtext",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 11:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,199, "T: Terminal",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 12:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,199, "Y: Script Box",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 13:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,199, "U: Warp Token",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 14:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,199, "I: Warp Lines",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 15:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,199, "O: Crewmate",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 16:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,199, "P: Start Point",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-03-31 21:52:10 +02:00
|
|
|
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
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
//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++;
|
|
|
|
}
|
2020-03-31 21:52:10 +02:00
|
|
|
if (graphics.translucentroomname)
|
2020-01-25 05:43:04 +01:00
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.footerrect.y = 230+ed.roomnamehide;
|
|
|
|
SDL_BlitSurface(graphics.footerbuffer, NULL, graphics.backBuffer, &graphics.footerrect);
|
2020-01-25 05:43:04 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
FillRect(graphics.backBuffer, 0,230+ed.roomnamehide,320,10, graphics.getRGB(0,0,0));
|
2020-01-25 05:43:04 +01:00
|
|
|
}
|
2020-03-31 21:52:10 +02:00
|
|
|
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
|
|
|
|
{
|
2020-03-31 21:52:10 +02:00
|
|
|
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)
|
|
|
|
{
|
2021-01-11 05:26:21 +01:00
|
|
|
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);
|
2020-03-31 21:52:10 +02:00
|
|
|
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:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,2, "1: Walls",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 1:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,2, "2: Backing",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 2:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,2, "3: Spikes",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 3:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,2, "4: Trinkets",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 4:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,2, "5: Checkpoint",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 5:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,2, "6: Disappear",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 6:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,2, "7: Conveyors",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 7:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,2, "8: Moving",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 8:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,2, "9: Enemies",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 9:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,2, "0: Grav Line",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 10:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,2, "R: Roomtext",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 11:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,2, "T: Terminal",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 12:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,2, "Y: Script Box",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 13:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,2, "U: Warp Token",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 14:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,2, "I: Warp Lines",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 15:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,2, "O: Crewmate",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
case 16:
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.bprint(2,2, "P: Start Point",196, 196, 255 - help.glow);
|
2020-01-01 21:29:24 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-02 19:42:39 +02:00
|
|
|
if(ed.notedelay>0 || ed.oldnotedelay>0)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-05-02 19:42:39 +02:00
|
|
|
float alpha = graphics.lerp(ed.oldnotedelay, ed.notedelay);
|
2020-03-31 21:52:10 +02:00
|
|
|
FillRect(graphics.backBuffer, 0,115,320,18, graphics.getRGB(92,92,92));
|
|
|
|
FillRect(graphics.backBuffer, 0,116,320,16, graphics.getRGB(0,0,0));
|
2020-05-02 19:42:39 +02:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.drawfade();
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-04-27 04:29:40 +02:00
|
|
|
graphics.render();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
Explicitly declare void for all void parameter functions (#628)
Apparently in C, if you have `void test();`, it's completely okay to do
`test(2);`. The function will take in the argument, but just discard it
and throw it away. It's like a trash can, and a rude one at that. If you
declare it like `void test(void);`, this is prevented.
This is not a problem in C++ - doing `void test();` and `test(2);` is
guaranteed to result in a compile error (this also means that right now,
at least in all `.cpp` files, nobody is ever calling a void parameter
function with arguments and having their arguments be thrown away).
However, we may not be using C++ in the future, so I just want to lay
down the precedent that if a function takes in no arguments, you must
explicitly declare it as such.
I would've added `-Wstrict-prototypes`, but it produces an annoying
warning message saying it doesn't work in C++ mode if you're compiling
in C++ mode. So it can be added later.
2021-02-25 23:23:59 +01:00
|
|
|
void editorrenderfixed(void)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-09-28 04:15:06 +02:00
|
|
|
extern editorclass ed;
|
2020-05-02 22:53:19 +02:00
|
|
|
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
|
|
|
|
2020-06-13 02:24:08 +02:00
|
|
|
if (game.ghostsenabled)
|
|
|
|
{
|
2020-06-13 02:34:19 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-06-13 02:24:08 +02:00
|
|
|
if (ed.currentghosts + 1 < (int)ed.ghosts.size()) {
|
|
|
|
ed.currentghosts++;
|
|
|
|
if (ed.zmod) ed.currentghosts++;
|
|
|
|
} else {
|
|
|
|
ed.currentghosts = (int)ed.ghosts.size() - 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-29 19:37:56 +02:00
|
|
|
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;
|
2020-04-29 20:06:03 +02:00
|
|
|
case 2:
|
|
|
|
graphics.rcol=ed.getwarpbackground(ed.levx, ed.levy);
|
|
|
|
graphics.updatebackground(4);
|
|
|
|
break;
|
2020-04-30 00:37:39 +02:00
|
|
|
case 3:
|
|
|
|
graphics.rcol=ed.getwarpbackground(ed.levx, ed.levy);
|
|
|
|
graphics.updatebackground(5);
|
|
|
|
break;
|
2020-04-29 19:37:56 +02:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2020-04-30 00:25:01 +02:00
|
|
|
else if (!game.colourblindmode)
|
|
|
|
{
|
2020-11-03 00:23:53 +01:00
|
|
|
graphics.updatetowerbackground(graphics.titlebg);
|
2020-04-30 00:25:01 +02:00
|
|
|
}
|
2020-11-08 00:47:49 +01:00
|
|
|
}
|
|
|
|
|
Explicitly declare void for all void parameter functions (#628)
Apparently in C, if you have `void test();`, it's completely okay to do
`test(2);`. The function will take in the argument, but just discard it
and throw it away. It's like a trash can, and a rude one at that. If you
declare it like `void test(void);`, this is prevented.
This is not a problem in C++ - doing `void test();` and `test(2);` is
guaranteed to result in a compile error (this also means that right now,
at least in all `.cpp` files, nobody is ever calling a void parameter
function with arguments and having their arguments be thrown away).
However, we may not be using C++ in the future, so I just want to lay
down the precedent that if a function takes in no arguments, you must
explicitly declare it as such.
I would've added `-Wstrict-prototypes`, but it produces an annoying
warning message saying it doesn't work in C++ mode if you're compiling
in C++ mode. So it can be added later.
2021-02-25 23:23:59 +01:00
|
|
|
void editorlogic(void)
|
2020-11-08 00:47:49 +01:00
|
|
|
{
|
|
|
|
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--;
|
|
|
|
}
|
2020-04-29 19:37:56 +02:00
|
|
|
|
2020-03-31 21:52:10 +02:00
|
|
|
if (graphics.fademode == 1)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
//Return to game
|
2020-11-03 00:23:53 +01:00
|
|
|
graphics.titlebg.colstate = 10;
|
2020-01-01 21:29:24 +01:00
|
|
|
map.nexttowercolour();
|
Clean up all exit paths to the menu to use common code
There are multiple different exit paths to the main menu. In 2.2, they
all had a bunch of copy-pasted code. In 2.3 currently, most of them use
game.quittomenu(), but there are some stragglers that still use
hand-copied code.
This is a bit of a problem, because all exit paths should consistently
have FILESYSTEM_unmountassets(), as part of the 2.3 feature of per-level
custom assets. Furthermore, most (but not all) of the paths call
script.hardreset() too, and some of the stragglers don't. So there could
be something persisting through to the title screen (like a really long
flash/shake timer) that could only persist if exiting to the title
screen through those paths.
But, actually, it seems like there's a good reason for some of those to
not call script.hardreset() - namely, dying or completing No Death Mode
and completing a Time Trial presents some information onscreen that
would get reset by script.hardreset(), so I'll fix that in a later
commit.
So what I've done for this commit is found every exit path that didn't
already use game.quittomenu(), and made them use game.quittomenu(). As
well, some of them had special handling that existed on top of them
already having a corresponding entry in game.quittomenu() (but the path
would take the special handling because it never did game.quittomenu()),
so I removed that special handling as well (e.g. exiting from a custom
level used returntomenu(Menu::levellist) when quittomenu() already had
that same returntomenu()).
The menu that exiting from the level editor returns to is now handled in
game.quittomenu() as well, where the map.custommode branch now also
checks for map.custommodeforreal. Unfortunately, it seems like entering
the level editor doesn't properly initialize map.custommode, so entering
the level editor now initializes map.custommode, too.
I've also taken the music.play(6) out of game.quittomenu(), because not
all exit paths immediately play Presenting VVVVVV, so all exit paths
that DO immediately play Presenting VVVVVV now have music.play(6)
special-cased for them, which is fine enough for me.
Here is the list of all exit paths to the menu:
- Exiting through the pause menu (without glitchrunner mode)
- Exiting through the pause menu (with glitchrunner mode)
- Completing a custom level
- Completing a Time Trial
- Dying in No Death Mode
- Completing No Death Mode
- Completing an Intermission replay
- Exiting from the level editor
- Completing the main game
2021-01-07 23:20:37 +01:00
|
|
|
game.quittomenu();
|
|
|
|
music.play(6); //should be before game.quittomenu()
|
2020-01-01 21:29:24 +01:00
|
|
|
ed.settingsmod=false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-01-10 18:14:37 +01:00
|
|
|
static void editormenuactionpress()
|
2020-04-16 05:52:21 +02:00
|
|
|
{
|
2020-09-28 04:15:06 +02:00
|
|
|
extern editorclass ed;
|
2020-04-17 00:19:17 +02:00
|
|
|
switch (game.currentmenuname)
|
2020-04-16 05:52:21 +02:00
|
|
|
{
|
2020-04-17 00:19:17 +02:00
|
|
|
case Menu::ed_desc:
|
2020-04-16 06:01:00 +02:00
|
|
|
switch (game.currentmenuoption)
|
2020-04-16 05:52:21 +02:00
|
|
|
{
|
2020-04-16 06:01:00 +02:00
|
|
|
case 0:
|
2020-04-16 05:52:21 +02:00
|
|
|
ed.titlemod=true;
|
|
|
|
key.enabletextentry();
|
|
|
|
key.keybuffer=EditorData::GetInstance().title;
|
2020-04-16 06:01:00 +02:00
|
|
|
break;
|
|
|
|
case 1:
|
2020-04-16 05:52:21 +02:00
|
|
|
ed.creatormod=true;
|
|
|
|
key.enabletextentry();
|
|
|
|
key.keybuffer=EditorData::GetInstance().creator;
|
2020-04-16 06:01:00 +02:00
|
|
|
break;
|
|
|
|
case 2:
|
2020-04-16 05:52:21 +02:00
|
|
|
ed.desc1mod=true;
|
|
|
|
key.enabletextentry();
|
|
|
|
key.keybuffer=ed.Desc1;
|
2020-04-16 06:01:00 +02:00
|
|
|
break;
|
|
|
|
case 3:
|
2020-04-16 05:52:21 +02:00
|
|
|
ed.websitemod=true;
|
|
|
|
key.enabletextentry();
|
|
|
|
key.keybuffer=ed.website;
|
2020-04-16 06:01:00 +02:00
|
|
|
break;
|
|
|
|
case 4:
|
2020-04-16 05:52:21 +02:00
|
|
|
music.playef(11);
|
2020-04-26 04:25:30 +02:00
|
|
|
game.returnmenu();
|
2020-04-16 05:52:21 +02:00
|
|
|
map.nexttowercolour();
|
2020-04-16 06:01:00 +02:00
|
|
|
break;
|
2020-04-16 05:52:21 +02:00
|
|
|
}
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::ed_settings:
|
2020-04-16 06:01:00 +02:00
|
|
|
switch (game.currentmenuoption)
|
2020-04-16 05:52:21 +02:00
|
|
|
{
|
2020-04-16 06:01:00 +02:00
|
|
|
case 0:
|
2020-04-16 05:52:21 +02:00
|
|
|
//Change level description stuff
|
|
|
|
music.playef(11);
|
2020-04-16 06:53:36 +02:00
|
|
|
game.createmenu(Menu::ed_desc);
|
2020-04-16 05:52:21 +02:00
|
|
|
map.nexttowercolour();
|
2020-04-16 06:01:00 +02:00
|
|
|
break;
|
|
|
|
case 1:
|
2020-04-16 05:52:21 +02:00
|
|
|
//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;
|
2020-04-16 06:01:00 +02:00
|
|
|
break;
|
|
|
|
case 2:
|
2020-04-16 05:52:21 +02:00
|
|
|
music.playef(11);
|
2020-04-16 06:53:36 +02:00
|
|
|
game.createmenu(Menu::ed_music);
|
2020-04-16 05:52:21 +02:00
|
|
|
map.nexttowercolour();
|
|
|
|
if(ed.levmusic>0) music.play(ed.levmusic);
|
2020-04-16 06:01:00 +02:00
|
|
|
break;
|
|
|
|
case 3:
|
Add a player trail to the editor (ghosts)
A few months ago, I added ghosts to the VVVVVV: Community Edition editor. I was told recently I should think
about upstreaming it, and with Terry saying go ahead I finally ported them into VVVVVV. There's one slight
difference however--you can choose whether you have them or not in the editor's settings menu. They're off by
default, and this is saved to the save file.
Anyway, when you're playtesting, the game saves the players position, color, room coordinates and sprite every 3
frames. The max is 100, where if it tries to add more, the oldest one gets removed.
When you exit playtesting, the saved positions appear one at a time, and you can use the Z key to speed it up.
[Here's a video of them in action.](https://o.lol-sa.me/4H21zCv.mp4)
2020-06-13 00:04:35 +02:00
|
|
|
music.playef(11);
|
|
|
|
game.ghostsenabled = !game.ghostsenabled;
|
|
|
|
break;
|
|
|
|
case 4:
|
2020-04-16 05:52:21 +02:00
|
|
|
//Load level
|
|
|
|
ed.settingsmod=false;
|
|
|
|
graphics.backgrounddrawn=false;
|
|
|
|
map.nexttowercolour();
|
|
|
|
|
2020-07-01 08:17:26 +02:00
|
|
|
ed.keydelay = 6;
|
|
|
|
ed.getlin(TEXT_LOAD, "Enter map filename to load:", &(ed.filename));
|
2020-04-16 05:52:21 +02:00
|
|
|
game.mapheld=true;
|
|
|
|
graphics.backgrounddrawn=false;
|
2020-04-16 06:01:00 +02:00
|
|
|
break;
|
Add a player trail to the editor (ghosts)
A few months ago, I added ghosts to the VVVVVV: Community Edition editor. I was told recently I should think
about upstreaming it, and with Terry saying go ahead I finally ported them into VVVVVV. There's one slight
difference however--you can choose whether you have them or not in the editor's settings menu. They're off by
default, and this is saved to the save file.
Anyway, when you're playtesting, the game saves the players position, color, room coordinates and sprite every 3
frames. The max is 100, where if it tries to add more, the oldest one gets removed.
When you exit playtesting, the saved positions appear one at a time, and you can use the Z key to speed it up.
[Here's a video of them in action.](https://o.lol-sa.me/4H21zCv.mp4)
2020-06-13 00:04:35 +02:00
|
|
|
case 5:
|
2020-04-16 05:52:21 +02:00
|
|
|
//Save level
|
|
|
|
ed.settingsmod=false;
|
|
|
|
map.nexttowercolour();
|
|
|
|
|
2020-07-01 08:17:26 +02:00
|
|
|
ed.keydelay = 6;
|
|
|
|
ed.getlin(TEXT_SAVE, "Enter map filename to save as:", &(ed.filename));
|
2020-04-16 05:52:21 +02:00
|
|
|
game.mapheld=true;
|
|
|
|
graphics.backgrounddrawn=false;
|
2020-04-16 06:01:00 +02:00
|
|
|
break;
|
Add a player trail to the editor (ghosts)
A few months ago, I added ghosts to the VVVVVV: Community Edition editor. I was told recently I should think
about upstreaming it, and with Terry saying go ahead I finally ported them into VVVVVV. There's one slight
difference however--you can choose whether you have them or not in the editor's settings menu. They're off by
default, and this is saved to the save file.
Anyway, when you're playtesting, the game saves the players position, color, room coordinates and sprite every 3
frames. The max is 100, where if it tries to add more, the oldest one gets removed.
When you exit playtesting, the saved positions appear one at a time, and you can use the Z key to speed it up.
[Here's a video of them in action.](https://o.lol-sa.me/4H21zCv.mp4)
2020-06-13 00:04:35 +02:00
|
|
|
case 6:
|
2020-04-16 05:52:21 +02:00
|
|
|
music.playef(11);
|
2020-04-16 06:53:36 +02:00
|
|
|
game.createmenu(Menu::ed_quit);
|
2020-04-16 05:52:21 +02:00
|
|
|
map.nexttowercolour();
|
2020-04-16 06:01:00 +02:00
|
|
|
break;
|
2020-04-16 05:52:21 +02:00
|
|
|
}
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::ed_music:
|
2020-04-16 06:01:00 +02:00
|
|
|
switch (game.currentmenuoption)
|
2020-04-16 05:52:21 +02:00
|
|
|
{
|
2020-04-16 06:01:00 +02:00
|
|
|
case 0:
|
2020-04-16 05:52:21 +02:00
|
|
|
ed.levmusic++;
|
2020-07-01 03:40:44 +02:00
|
|
|
if(ed.levmusic==16) ed.levmusic=0;
|
2020-04-16 05:52:21 +02:00
|
|
|
if(ed.levmusic>0)
|
|
|
|
{
|
|
|
|
music.play(ed.levmusic);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
music.haltdasmusik();
|
|
|
|
}
|
|
|
|
music.playef(11);
|
2020-04-16 06:01:00 +02:00
|
|
|
break;
|
|
|
|
case 1:
|
2020-04-16 05:52:21 +02:00
|
|
|
music.playef(11);
|
|
|
|
music.fadeout();
|
2020-04-26 04:25:30 +02:00
|
|
|
game.returnmenu();
|
2020-04-16 05:52:21 +02:00
|
|
|
map.nexttowercolour();
|
2020-04-16 06:01:00 +02:00
|
|
|
break;
|
2020-04-16 05:52:21 +02:00
|
|
|
}
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
case Menu::ed_quit:
|
2020-04-16 06:01:00 +02:00
|
|
|
switch (game.currentmenuoption)
|
2020-04-16 05:52:21 +02:00
|
|
|
{
|
2020-04-16 06:01:00 +02:00
|
|
|
case 0:
|
2020-04-16 05:52:21 +02:00
|
|
|
//Saving and quit
|
|
|
|
ed.saveandquit=true;
|
|
|
|
|
|
|
|
ed.settingsmod=false;
|
|
|
|
map.nexttowercolour();
|
|
|
|
|
2020-07-01 08:17:26 +02:00
|
|
|
ed.keydelay = 6;
|
|
|
|
ed.getlin(TEXT_SAVE, "Enter map filename to save as:", &(ed.filename));
|
2020-04-16 05:52:21 +02:00
|
|
|
game.mapheld=true;
|
|
|
|
graphics.backgrounddrawn=false;
|
2020-04-16 06:01:00 +02:00
|
|
|
break;
|
|
|
|
case 1:
|
2020-04-16 05:52:21 +02:00
|
|
|
//Quit without saving
|
|
|
|
music.playef(11);
|
|
|
|
music.fadeout();
|
|
|
|
graphics.fademode = 2;
|
2020-04-16 06:01:00 +02:00
|
|
|
break;
|
|
|
|
case 2:
|
2020-04-16 05:52:21 +02:00
|
|
|
//Go back to editor
|
|
|
|
music.playef(11);
|
2020-04-26 04:25:30 +02:00
|
|
|
game.returnmenu();
|
2020-04-16 05:52:21 +02:00
|
|
|
map.nexttowercolour();
|
2020-04-16 06:01:00 +02:00
|
|
|
break;
|
2020-04-16 05:52:21 +02:00
|
|
|
}
|
2020-04-17 00:19:17 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
2020-04-16 05:52:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Explicitly declare void for all void parameter functions (#628)
Apparently in C, if you have `void test();`, it's completely okay to do
`test(2);`. The function will take in the argument, but just discard it
and throw it away. It's like a trash can, and a rude one at that. If you
declare it like `void test(void);`, this is prevented.
This is not a problem in C++ - doing `void test();` and `test(2);` is
guaranteed to result in a compile error (this also means that right now,
at least in all `.cpp` files, nobody is ever calling a void parameter
function with arguments and having their arguments be thrown away).
However, we may not be using C++ in the future, so I just want to lay
down the precedent that if a function takes in no arguments, you must
explicitly declare it as such.
I would've added `-Wstrict-prototypes`, but it produces an annoying
warning message saying it doesn't work in C++ mode if you're compiling
in C++ mode. So it can be added later.
2021-02-25 23:23:59 +01:00
|
|
|
void editorinput(void)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-09-28 04:15:06 +02: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;
|
2020-11-13 01:45:51 +01:00
|
|
|
if (graphics.screenbuffer->stretchMode == 1) {
|
2020-01-12 10:09:32 +01:00
|
|
|
// In this mode specifically, we have to fix the mouse coordinates
|
|
|
|
int winwidth, winheight;
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.screenbuffer->GetWindowSize(&winwidth, &winheight);
|
2020-01-12 10:09:32 +01:00
|
|
|
ed.tilex = ed.tilex * 320 / winwidth;
|
|
|
|
ed.tiley = ed.tiley * 240 / winheight;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-01 04:40:24 +02: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-07-01 02:39:32 +02:00
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
game.press_left = false;
|
|
|
|
game.press_right = false;
|
|
|
|
game.press_action = false;
|
|
|
|
game.press_map = false;
|
|
|
|
|
2020-07-01 02:39:32 +02:00
|
|
|
if (key.isDown(KEYBOARD_LEFT) || key.isDown(KEYBOARD_a) || key.controllerWantsLeft(false))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
game.press_left = true;
|
|
|
|
}
|
2020-07-01 02:39:32 +02:00
|
|
|
if (key.isDown(KEYBOARD_RIGHT) || key.isDown(KEYBOARD_d) || key.controllerWantsRight(false))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
game.press_right = true;
|
|
|
|
}
|
2020-07-01 02:39:32 +02:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2020-06-01 01:31:02 +02:00
|
|
|
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;
|
2020-07-01 08:17:26 +02:00
|
|
|
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;
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.backgrounddrawn=false;
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-04-26 04:37:33 +02: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--;
|
|
|
|
|
2020-07-01 02:39:32 +02:00
|
|
|
if(up_pressed && ed.keydelay<=0)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
ed.keydelay=6;
|
|
|
|
ed.hookmenu--;
|
|
|
|
}
|
|
|
|
|
2020-07-01 02:39:32 +02:00
|
|
|
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;
|
|
|
|
|
2020-04-03 21:23:37 +02:00
|
|
|
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
|
2020-07-01 02:39:32 +02:00
|
|
|
&& !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
|
2020-07-01 02:39:32 +02:00
|
|
|
|| 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;
|
2020-08-18 00:39:19 +02:00
|
|
|
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];
|
2020-01-31 19:25:37 +01:00
|
|
|
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--;
|
|
|
|
|
2020-07-01 02:39:32 +02:00
|
|
|
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];
|
|
|
|
}
|
|
|
|
|
2020-07-01 02:39:32 +02:00
|
|
|
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];
|
2020-01-19 03:39:39 +01:00
|
|
|
ed.keydelay=6;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-09-28 00:14:42 +02: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;
|
2020-01-31 19:25:37 +01:00
|
|
|
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];
|
2020-01-31 19:25:37 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-07-01 08:17:26 +02:00
|
|
|
else if (ed.textmod)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-07-01 08:17:26 +02:00
|
|
|
*ed.textptr = key.keybuffer;
|
|
|
|
|
|
|
|
if (!game.press_map && !key.isDown(27))
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-07-01 08:17:26 +02:00
|
|
|
game.mapheld = false;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-07-01 08:17:26 +02:00
|
|
|
if (!game.mapheld && game.press_map)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-07-01 08:17:26 +02:00
|
|
|
game.mapheld = true;
|
|
|
|
key.disabletextentry();
|
|
|
|
switch (ed.textmod)
|
|
|
|
{
|
2020-07-01 08:35:59 +02:00
|
|
|
case TEXT_GOTOROOM:
|
|
|
|
{
|
2021-02-12 02:02:32 +01:00
|
|
|
char coord_x[16];
|
|
|
|
char coord_y[16];
|
|
|
|
|
|
|
|
const char* comma = SDL_strchr(key.keybuffer.c_str(), ',');
|
|
|
|
|
|
|
|
bool valid_input = comma != NULL;
|
|
|
|
|
|
|
|
if (valid_input)
|
|
|
|
{
|
|
|
|
SDL_strlcpy(
|
|
|
|
coord_x,
|
|
|
|
key.keybuffer.c_str(),
|
|
|
|
VVV_min(comma - key.keybuffer.c_str() + 1, sizeof(coord_x))
|
|
|
|
);
|
|
|
|
SDL_strlcpy(coord_y, &comma[1], sizeof(coord_y));
|
|
|
|
|
|
|
|
valid_input = is_number(coord_x) && is_number(coord_y);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!valid_input)
|
2020-07-01 08:35:59 +02:00
|
|
|
{
|
|
|
|
ed.note = "[ ERROR: Invalid format ]";
|
|
|
|
ed.notedelay = 45;
|
|
|
|
break;
|
|
|
|
}
|
2021-02-12 02:02:32 +01:00
|
|
|
|
|
|
|
ed.levx = clamp(help.Int(coord_x) - 1, 0, ed.mapwidth - 1);
|
|
|
|
ed.levy = clamp(help.Int(coord_y) - 1, 0, ed.mapheight - 1);
|
2020-07-01 08:35:59 +02:00
|
|
|
graphics.backgrounddrawn = false;
|
|
|
|
break;
|
|
|
|
}
|
2020-07-01 08:17:26 +02:00
|
|
|
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
|
|
|
}
|
2020-07-01 08:17:26 +02: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())
|
2020-07-01 08:17:26 +02:00
|
|
|
{
|
|
|
|
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;
|
2020-07-01 08:17:26 +02:00
|
|
|
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
|
2020-07-01 02:39:32 +02:00
|
|
|
&& !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
|
2020-07-01 02:39:32 +02:00
|
|
|
|| up_pressed || down_pressed)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
game.jumpheld = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(game.menustart)
|
|
|
|
{
|
2020-07-01 02:39:32 +02:00
|
|
|
if (game.press_left || up_pressed)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
game.currentmenuoption--;
|
|
|
|
}
|
2020-07-01 02:39:32 +02:00
|
|
|
else if (game.press_right || down_pressed)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
game.currentmenuoption++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Refactor menu creation code
Firstly, menu options are no longer ad-hoc objects, and are added by
using Game::option() (this is the biggest change). This removes the
vector Game::menuoptionsactive, and Game::menuoptions is now a vector of
MenuOption instead of std::string.
Secondly, the manual tracker variable of the amount of menu options,
Game::nummenuoptions, has been removed, in favor of using vectors
properly and using Game::menuoptions::size().
As a result, a lot of copy-pasted code has been removed from
Game::createmenu(), mostly due to having to have different versions of
menus depending on whether or not we have certain defines, or having an
mmmmmm.vvv file inside the VVVVVV directory. In the old days, you
couldn't just add or remove a menu option conveniently, you had to
shuffle around the position of every other menu option too, which
resulted in lots of copy-pasted code. But now this copy-pasted code has
been de-duplicated, at least in Game::createmenu().
2020-04-15 06:50:17 +02:00
|
|
|
if (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)
|
|
|
|
{
|
2020-04-16 05:52:21 +02:00
|
|
|
editormenuactionpress();
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-07-01 05:39:36 +02: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
|
2020-07-01 06:53:11 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-07-01 05:39:36 +02:00
|
|
|
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
|
|
|
|
{
|
2020-07-01 05:39:36 +02:00
|
|
|
// No modifiers
|
|
|
|
ed.shiftkey=false;
|
|
|
|
if(key.keymap[SDLK_F1])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-07-01 06:39:10 +02:00
|
|
|
ed.switch_tileset();
|
|
|
|
graphics.backgrounddrawn = false;
|
|
|
|
ed.keydelay = 6;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-07-01 05:39:36 +02:00
|
|
|
if(key.keymap[SDLK_F2])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-07-01 06:39:10 +02:00
|
|
|
ed.switch_tilecol();
|
|
|
|
graphics.backgrounddrawn = false;
|
|
|
|
ed.keydelay = 6;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-07-01 05:39:36 +02:00
|
|
|
if(key.keymap[SDLK_F3])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-07-01 06:39:10 +02:00
|
|
|
ed.switch_enemy();
|
2020-01-01 21:29:24 +01:00
|
|
|
ed.keydelay=6;
|
|
|
|
}
|
2020-07-01 05:39:36 +02:00
|
|
|
if(key.keymap[SDLK_F4])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
ed.keydelay=6;
|
|
|
|
ed.boundarytype=1;
|
|
|
|
ed.boundarymod=1;
|
|
|
|
}
|
2020-07-01 05:39:36 +02:00
|
|
|
if(key.keymap[SDLK_F5])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
ed.keydelay=6;
|
|
|
|
ed.boundarytype=2;
|
|
|
|
ed.boundarymod=1;
|
|
|
|
}
|
2020-07-01 05:39:36 +02:00
|
|
|
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";
|
|
|
|
}
|
2020-03-31 21:52:10 +02:00
|
|
|
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;
|
|
|
|
|
2020-07-01 05:39:36 +02:00
|
|
|
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;
|
|
|
|
}
|
2020-07-01 05:39:36 +02:00
|
|
|
if(key.keymap[SDLK_e])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-07-01 08:17:26 +02: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;
|
|
|
|
}
|
2020-07-01 08:35:59 +02:00
|
|
|
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
|
2020-07-01 05:39:36 +02:00
|
|
|
if(key.keymap[SDLK_s])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-07-01 08:17:26 +02: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;
|
|
|
|
}
|
|
|
|
|
2020-07-01 05:39:36 +02:00
|
|
|
if(key.keymap[SDLK_l])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-07-01 08:17:26 +02: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?
|
2020-03-01 21:24:43 +01:00
|
|
|
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
|
2020-03-01 21:24:43 +01:00
|
|
|
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
|
|
|
|
{
|
Add a player trail to the editor (ghosts)
A few months ago, I added ghosts to the VVVVVV: Community Edition editor. I was told recently I should think
about upstreaming it, and with Terry saying go ahead I finally ported them into VVVVVV. There's one slight
difference however--you can choose whether you have them or not in the editor's settings menu. They're off by
default, and this is saved to the save file.
Anyway, when you're playtesting, the game saves the players position, color, room coordinates and sprite every 3
frames. The max is 100, where if it tries to add more, the oldest one gets removed.
When you exit playtesting, the saved positions appear one at a time, and you can use the Z key to speed it up.
[Here's a video of them in action.](https://o.lol-sa.me/4H21zCv.mp4)
2020-06-13 00:04:35 +02:00
|
|
|
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;
|
Fix spawning from an exotic checkpoint setting invalid gravitycontrol
An exotic checkpoint is a checkpoint with a sprite that is neither the
flipped checkpoint nor unflipped checkpoint. More specifically, it's a
checkpoint whose edentity p1 attribute is something other than 0 or 1.
Normally, whenever you touch an exotic checkpoint in-game, your
savepoint's y-position and gravitycontrol don't get touched. However, in
the editor, spawning from an exotic checkpoint means that your
gravitycontrol gets set to a value that is neither 0 nor 1. In this
invalid gravitycontrol state, Viridian is treated like they're flipped,
but they cannot unflip.
This is an inconsistency between the editor and in-game, so I'm fixing
it. Now, spawning from an exotic checkpoint in the editor will just set
your gravitycontrol to 0, i.e. unflipped.
2020-10-06 08:04:58 +02:00
|
|
|
if (edentity[testeditor].p1 == 0) // NOT a bool check!
|
|
|
|
{
|
|
|
|
game.edsavegc = 1;
|
2020-10-06 08:13:07 +02:00
|
|
|
game.edsavey -= 2;
|
Fix spawning from an exotic checkpoint setting invalid gravitycontrol
An exotic checkpoint is a checkpoint with a sprite that is neither the
flipped checkpoint nor unflipped checkpoint. More specifically, it's a
checkpoint whose edentity p1 attribute is something other than 0 or 1.
Normally, whenever you touch an exotic checkpoint in-game, your
savepoint's y-position and gravitycontrol don't get touched. However, in
the editor, spawning from an exotic checkpoint means that your
gravitycontrol gets set to a value that is neither 0 nor 1. In this
invalid gravitycontrol state, Viridian is treated like they're flipped,
but they cannot unflip.
This is an inconsistency between the editor and in-game, so I'm fixing
it. Now, spawning from an exotic checkpoint in the editor will just set
your gravitycontrol to 0, i.e. unflipped.
2020-10-06 08:04:58 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
game.edsavegc = 0;
|
2020-10-06 08:13:07 +02:00
|
|
|
game.edsavey -= 7;
|
Fix spawning from an exotic checkpoint setting invalid gravitycontrol
An exotic checkpoint is a checkpoint with a sprite that is neither the
flipped checkpoint nor unflipped checkpoint. More specifically, it's a
checkpoint whose edentity p1 attribute is something other than 0 or 1.
Normally, whenever you touch an exotic checkpoint in-game, your
savepoint's y-position and gravitycontrol don't get touched. However, in
the editor, spawning from an exotic checkpoint means that your
gravitycontrol gets set to a value that is neither 0 nor 1. In this
invalid gravitycontrol state, Viridian is treated like they're flipped,
but they cannot unflip.
This is an inconsistency between the editor and in-game, so I'm fixing
it. Now, spawning from an exotic checkpoint in the editor will just set
your gravitycontrol to 0, i.e. unflipped.
2020-10-06 08:04:58 +02:00
|
|
|
}
|
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;
|
2020-10-06 08:13:07 +02:00
|
|
|
game.edsavey++;
|
2020-01-01 21:29:24 +01:00
|
|
|
game.edsavedir=1-edentity[testeditor].p1;
|
|
|
|
}
|
|
|
|
|
2020-04-04 00:44:04 +02:00
|
|
|
music.haltdasmusik();
|
2020-03-31 21:52:10 +02:00
|
|
|
graphics.backgrounddrawn=false;
|
2020-02-10 03:23:12 +01:00
|
|
|
ed.returneditoralpha = 1000; // Let's start it higher than 255 since it gets clamped
|
2020-05-02 19:49:41 +02:00
|
|
|
ed.oldreturneditoralpha = 1000;
|
2020-03-31 21:38:52 +02:00
|
|
|
script.startgamemode(21);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-17 23:49:57 +02: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
|
|
|
|
2020-07-01 05:39:36 +02:00
|
|
|
if(key.keymap[SDLK_COMMA])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-07-01 05:39:36 +02:00
|
|
|
ed.drawmode--;
|
|
|
|
ed.keydelay=6;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
2020-07-01 05:39:36 +02:00
|
|
|
else if(key.keymap[SDLK_PERIOD])
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-07-01 05:39:36 +02:00
|
|
|
ed.drawmode++;
|
|
|
|
ed.keydelay=6;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-07-01 05:39:36 +02: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
|
|
|
|
2020-07-01 05:39:36 +02: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
|
|
|
|
2020-07-01 05:39:36 +02: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
|
2020-07-01 08:17:26 +02:00
|
|
|
ed.lclickdelay=1;
|
2020-03-01 21:24:43 +01:00
|
|
|
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);
|
2020-07-01 08:17:26 +02:00
|
|
|
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)
|
|
|
|
{
|
2020-06-17 23:49:57 +02:00
|
|
|
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
|
|
|
|
{
|
2020-06-17 23:49:57 +02:00
|
|
|
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
|
2020-06-17 23:49:57 +02:00
|
|
|
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)
|
|
|
|
{
|
2020-07-01 08:17:26 +02:00
|
|
|
ed.lclickdelay=1;
|
2020-03-01 21:24:43 +01:00
|
|
|
ed.textent=edentity.size();
|
2020-01-01 21:29:24 +01:00
|
|
|
addedentity(ed.tilex+ (ed.levx*40),ed.tiley+ (ed.levy*30),17);
|
2020-07-01 08:17:26 +02:00
|
|
|
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)
|
|
|
|
{
|
2020-04-09 07:09:11 +02:00
|
|
|
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
|
|
|
|
{
|
2020-01-23 15:43:53 +01:00
|
|
|
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)
|
|
|
|
{
|
2020-07-01 08:17:26 +02:00
|
|
|
ed.lclickdelay=1;
|
2020-03-01 21:24:43 +01:00
|
|
|
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);
|
2020-07-01 08:17:26 +02:00
|
|
|
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;
|
2020-03-01 21:24:43 +01:00
|
|
|
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
|
|
|
|
{
|
2020-04-09 07:13:43 +02:00
|
|
|
if(ed.numcrewmates()<100)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-05-20 03:18:01 +02: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
|
|
|
|
{
|
2020-01-23 15:43:53 +01:00
|
|
|
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
|
2020-03-01 21:24:43 +01:00
|
|
|
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)
|
|
|
|
{
|
Improve support for retextured checkpoints and terminals in the editor
Retextured checkpoints have always been in the game, but clicking on
them in the editor would lead to them losing their retextured-ness. So,
checkpoints should be left alone if their p1 isn't either 0 or 1. Also,
they don't show up properly in the editor, so that's fixed, too.
Retextured and flipped terminals were added in 2.3, and show up properly
in-game, but don't properly show up in the editor, either. So now they
show up in the editor. Additionally, clicking on them will flip the
terminal as well, but only if its p1 is 0 or 1, just like checkpoints
now do.
2021-01-12 02:19:20 +01:00
|
|
|
// If it's not textured as a checkpoint, leave it alone
|
|
|
|
if (edentity[tmp].p1 == 0 || edentity[tmp].p1 == 1)
|
|
|
|
{
|
|
|
|
edentity[tmp].p1=(edentity[tmp].p1+1)%2;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
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)
|
|
|
|
{
|
2020-07-01 08:17:26 +02:00
|
|
|
ed.getlin(TEXT_ROOMTEXT, "Enter roomtext:", &(edentity[tmp].scriptname));
|
2020-01-27 11:15:25 +01:00
|
|
|
ed.textent=tmp;
|
2020-01-01 21:29:24 +01:00
|
|
|
ed.lclickdelay=1;
|
|
|
|
}
|
2020-02-21 22:23:04 +01:00
|
|
|
else if(edentity[tmp].t==18 || edentity[tmp].t==19)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
ed.lclickdelay=1;
|
2020-07-01 08:17:26 +02:00
|
|
|
ed.textent=tmp;
|
|
|
|
ed.getlin(TEXT_SCRIPT, "Enter script name:", &(edentity[ed.textent].scriptname));
|
Improve support for retextured checkpoints and terminals in the editor
Retextured checkpoints have always been in the game, but clicking on
them in the editor would lead to them losing their retextured-ness. So,
checkpoints should be left alone if their p1 isn't either 0 or 1. Also,
they don't show up properly in the editor, so that's fixed, too.
Retextured and flipped terminals were added in 2.3, and show up properly
in-game, but don't properly show up in the editor, either. So now they
show up in the editor. Additionally, clicking on them will flip the
terminal as well, but only if its p1 is 0 or 1, just like checkpoints
now do.
2021-01-12 02:19:20 +01:00
|
|
|
if (edentity[tmp].t == 18
|
|
|
|
&& (edentity[tmp].p1 == 0 || edentity[tmp].p1 == 1))
|
|
|
|
{
|
|
|
|
// Flip the terminal, but if it's not textured as a terminal leave it alone
|
|
|
|
edentity[tmp].p1 = (edentity[tmp].p1 + 1) % 2;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ed.lclickdelay=0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(key.rightbutton)
|
|
|
|
{
|
|
|
|
//place tiles
|
2020-08-01 21:23:56 +02:00
|
|
|
if(ed.drawmode < 2) {
|
|
|
|
if(ed.bmod)
|
2020-06-17 23:49:57 +02:00
|
|
|
{
|
2020-08-01 21:23:56 +02:00
|
|
|
for(int i=0; i<30; i++)
|
|
|
|
{
|
|
|
|
ed.placetilelocal(ed.tilex, i, 0);
|
|
|
|
}
|
2020-06-17 23:49:57 +02:00
|
|
|
}
|
2020-08-01 21:23:56 +02:00
|
|
|
else if(ed.hmod)
|
2020-06-17 23:49:57 +02:00
|
|
|
{
|
2020-08-01 21:23:56 +02:00
|
|
|
for(int i=0; i<40; i++)
|
|
|
|
{
|
|
|
|
ed.placetilelocal(i, ed.tiley, 0);
|
|
|
|
}
|
2020-06-17 23:49:57 +02:00
|
|
|
}
|
2020-08-01 21:23:56 +02:00
|
|
|
else if(ed.vmod)
|
2020-06-17 23:49:57 +02:00
|
|
|
{
|
2020-08-01 21:23:56 +02:00
|
|
|
for(int j=-4; j<5; j++)
|
2020-06-17 23:49:57 +02:00
|
|
|
{
|
2020-08-01 21:23:56 +02:00
|
|
|
for(int i=-4; i<5; i++)
|
|
|
|
{
|
|
|
|
ed.placetilelocal(ed.tilex+i, ed.tiley+j, 0);
|
|
|
|
}
|
2020-06-17 23:49:57 +02:00
|
|
|
}
|
|
|
|
}
|
2020-08-01 21:23:56 +02:00
|
|
|
else if(ed.cmod)
|
2020-06-17 23:49:57 +02:00
|
|
|
{
|
2020-08-01 21:23:56 +02:00
|
|
|
for(int j=-3; j<4; j++)
|
2020-06-17 23:49:57 +02:00
|
|
|
{
|
2020-08-01 21:23:56 +02:00
|
|
|
for(int i=-3; i<4; i++)
|
|
|
|
{
|
|
|
|
ed.placetilelocal(ed.tilex+i, ed.tiley+j, 0);
|
|
|
|
}
|
2020-06-17 23:49:57 +02:00
|
|
|
}
|
|
|
|
}
|
2020-08-01 21:23:56 +02:00
|
|
|
else if(ed.xmod)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-08-01 21:23:56 +02:00
|
|
|
for(int j=-2; j<3; j++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-08-01 21:23:56 +02: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
|
|
|
}
|
|
|
|
}
|
2020-08-01 21:23:56 +02:00
|
|
|
else if(ed.zmod)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-08-01 21:23:56 +02:00
|
|
|
for(int j=-1; j<2; j++)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
2020-08-01 21:23:56 +02: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
|
|
|
}
|
|
|
|
}
|
2020-08-01 21:23:56 +02: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);
|
|
|
|
}
|
2020-03-01 21:24:43 +01:00
|
|
|
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++)
|
|
|
|
{
|
2020-04-03 03:50:37 +02:00
|
|
|
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++)
|
|
|
|
{
|
2020-04-03 03:50:37 +02:00
|
|
|
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++)
|
|
|
|
{
|
2020-04-03 03:50:37 +02:00
|
|
|
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++)
|
|
|
|
{
|
2020-04-03 03:50:37 +02:00
|
|
|
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
|
2020-04-03 02:43:54 +02:00
|
|
|
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++)
|
|
|
|
{
|
2020-04-03 03:50:37 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-06-20 00:23:28 +02:00
|
|
|
#endif /* NO_EDITOR */
|
2020-02-10 01:53:01 +01:00
|
|
|
|
2020-06-30 20:47:22 +02:00
|
|
|
// 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
|
Explicitly declare void for all void parameter functions (#628)
Apparently in C, if you have `void test();`, it's completely okay to do
`test(2);`. The function will take in the argument, but just discard it
and throw it away. It's like a trash can, and a rude one at that. If you
declare it like `void test(void);`, this is prevented.
This is not a problem in C++ - doing `void test();` and `test(2);` is
guaranteed to result in a compile error (this also means that right now,
at least in all `.cpp` files, nobody is ever calling a void parameter
function with arguments and having their arguments be thrown away).
However, we may not be using C++ in the future, so I just want to lay
down the precedent that if a function takes in no arguments, you must
explicitly declare it as such.
I would've added `-Wstrict-prototypes`, but it produces an annoying
warning message saying it doesn't work in C++ mode if you're compiling
in C++ mode. So it can be added later.
2021-02-25 23:23:59 +01:00
|
|
|
Uint32 editorclass::getonewaycol(void)
|
2020-06-30 20:47:22 +02:00
|
|
|
{
|
|
|
|
if (game.gamestate == EDITORMODE)
|
2020-09-28 04:15:06 +02:00
|
|
|
return getonewaycol(levx, levy);
|
2020-06-30 20:47:22 +02:00
|
|
|
else if (map.custommode)
|
|
|
|
return getonewaycol(game.roomx - 100, game.roomy - 100);
|
|
|
|
|
|
|
|
// Uh, I guess return solid white
|
|
|
|
return graphics.getRGB(255, 255, 255);
|
|
|
|
}
|
|
|
|
|
Explicitly declare void for all void parameter functions (#628)
Apparently in C, if you have `void test();`, it's completely okay to do
`test(2);`. The function will take in the argument, but just discard it
and throw it away. It's like a trash can, and a rude one at that. If you
declare it like `void test(void);`, this is prevented.
This is not a problem in C++ - doing `void test();` and `test(2);` is
guaranteed to result in a compile error (this also means that right now,
at least in all `.cpp` files, nobody is ever calling a void parameter
function with arguments and having their arguments be thrown away).
However, we may not be using C++ in the future, so I just want to lay
down the precedent that if a function takes in no arguments, you must
explicitly declare it as such.
I would've added `-Wstrict-prototypes`, but it produces an annoying
warning message saying it doesn't work in C++ mode if you're compiling
in C++ mode. So it can be added later.
2021-02-25 23:23:59 +01:00
|
|
|
int editorclass::numtrinkets(void)
|
2020-04-09 07:09:11 +02:00
|
|
|
{
|
|
|
|
int temp = 0;
|
|
|
|
for (size_t i = 0; i < edentity.size(); i++)
|
|
|
|
{
|
|
|
|
if (edentity[i].t == 9)
|
|
|
|
{
|
|
|
|
temp++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return temp;
|
|
|
|
}
|
|
|
|
|
Explicitly declare void for all void parameter functions (#628)
Apparently in C, if you have `void test();`, it's completely okay to do
`test(2);`. The function will take in the argument, but just discard it
and throw it away. It's like a trash can, and a rude one at that. If you
declare it like `void test(void);`, this is prevented.
This is not a problem in C++ - doing `void test();` and `test(2);` is
guaranteed to result in a compile error (this also means that right now,
at least in all `.cpp` files, nobody is ever calling a void parameter
function with arguments and having their arguments be thrown away).
However, we may not be using C++ in the future, so I just want to lay
down the precedent that if a function takes in no arguments, you must
explicitly declare it as such.
I would've added `-Wstrict-prototypes`, but it produces an annoying
warning message saying it doesn't work in C++ mode if you're compiling
in C++ mode. So it can be added later.
2021-02-25 23:23:59 +01:00
|
|
|
int editorclass::numcrewmates(void)
|
2020-04-09 07:13:43 +02:00
|
|
|
{
|
|
|
|
int temp = 0;
|
|
|
|
for (size_t i = 0; i < edentity.size(); i++)
|
|
|
|
{
|
|
|
|
if (edentity[i].t == 15)
|
|
|
|
{
|
|
|
|
temp++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return temp;
|
|
|
|
}
|
|
|
|
|
2020-02-11 03:29:29 +01:00
|
|
|
#endif /* NO_CUSTOM_LEVELS */
|