2021-02-20 05:51:25 +01:00
|
|
|
#if !defined(NO_CUSTOM_LEVELS)
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
#define CL_DEFINITION
|
2021-02-20 08:19:09 +01:00
|
|
|
#include "CustomLevels.h"
|
2021-02-20 05:51:25 +01:00
|
|
|
|
|
|
|
#include <physfs.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <string>
|
|
|
|
#include <tinyxml2.h>
|
|
|
|
#include <utf8/unchecked.h>
|
|
|
|
|
2022-12-01 07:30:16 +01:00
|
|
|
#include "Alloc.h"
|
2021-09-25 01:37:27 +02:00
|
|
|
#include "Constants.h"
|
2021-02-21 00:40:11 +01:00
|
|
|
#include "Editor.h"
|
2021-02-20 05:51:25 +01:00
|
|
|
#include "Enums.h"
|
|
|
|
#include "FileSystemUtils.h"
|
|
|
|
#include "Game.h"
|
|
|
|
#include "Graphics.h"
|
|
|
|
#include "GraphicsUtil.h"
|
|
|
|
#include "KeyPoll.h"
|
|
|
|
#include "Map.h"
|
|
|
|
#include "Script.h"
|
|
|
|
#include "UtilityClass.h"
|
|
|
|
#include "Vlogging.h"
|
|
|
|
#include "XMLUtils.h"
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
#define SCNx32 "x"
|
|
|
|
#define SCNu32 "u"
|
|
|
|
#else
|
|
|
|
#ifndef __STDC_FORMAT_MACROS
|
|
|
|
#define __STDC_FORMAT_MACROS
|
|
|
|
#endif
|
|
|
|
#ifndef _POSIX_SOURCE
|
|
|
|
#define _POSIX_SOURCE
|
|
|
|
#endif
|
|
|
|
#include <inttypes.h>
|
|
|
|
#endif
|
|
|
|
|
2021-09-25 01:37:27 +02:00
|
|
|
#define VMULT(y) (y * SCREEN_WIDTH_TILES * maxwidth)
|
|
|
|
#define Y_INBOUNDS(y) (y >= 0 && y < SCREEN_HEIGHT_TILES * maxheight)
|
|
|
|
|
2021-02-21 00:45:48 +01:00
|
|
|
RoomProperty::RoomProperty(void)
|
2021-02-20 05:51:25 +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;
|
|
|
|
}
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
customlevelclass::customlevelclass(void)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
// comparison, not case sensitive.
|
|
|
|
static bool compare_nocase (std::string first, std::string second)
|
|
|
|
{
|
|
|
|
unsigned int i=0;
|
|
|
|
while ( (i<first.length()) && (i<second.length()) )
|
|
|
|
{
|
|
|
|
if (SDL_tolower(first[i])<SDL_tolower(second[i]))
|
|
|
|
return true;
|
|
|
|
else if (SDL_tolower(first[i])>SDL_tolower(second[i]))
|
|
|
|
return false;
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
if (first.length()<second.length())
|
|
|
|
return true;
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void levelZipCallback(const char* filename)
|
|
|
|
{
|
|
|
|
if (!FILESYSTEM_isFile(filename))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (endsWith(filename, ".zip"))
|
|
|
|
{
|
|
|
|
FILESYSTEM_loadZip(filename);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
void customlevelclass::loadZips(void)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
FILESYSTEM_enumerateLevelDirFileNames(levelZipCallback);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void replace_all(std::string& str, const std::string& from, const std::string& to)
|
|
|
|
{
|
|
|
|
if (from.empty())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t start_pos = 0;
|
|
|
|
|
|
|
|
while ((start_pos = str.find(from, start_pos)) != std::string::npos)
|
|
|
|
{
|
|
|
|
str.replace(start_pos, from.length(), to);
|
|
|
|
start_pos += to.length(); //In case `to` contains `from`, like replacing 'x' with 'yx'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static std::string find_tag(const std::string& buf, const std::string& start, const std::string& end)
|
|
|
|
{
|
|
|
|
size_t tag = buf.find(start);
|
|
|
|
|
|
|
|
if (tag == std::string::npos)
|
|
|
|
{
|
|
|
|
//No start tag
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t tag_start = tag + start.size();
|
|
|
|
size_t tag_close = buf.find(end, tag_start);
|
|
|
|
|
|
|
|
if (tag_close == std::string::npos)
|
|
|
|
{
|
|
|
|
//No close tag
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t tag_len = tag_close - tag_start;
|
|
|
|
std::string value(buf.substr(tag_start, tag_len));
|
|
|
|
|
|
|
|
//Encode special XML entities
|
|
|
|
replace_all(value, """, "\"");
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
if (start_pos + 2 >= value.length())
|
|
|
|
{
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
bool hex = value[start_pos + 2] == 'x';
|
|
|
|
size_t end = value.find(';', start_pos);
|
|
|
|
|
|
|
|
if (end == std::string::npos)
|
|
|
|
{
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t real_start = start_pos + 2 + ((int) hex);
|
|
|
|
std::string number(value.substr(real_start, end - real_start));
|
|
|
|
|
|
|
|
if (!is_positive_num(number.c_str(), hex))
|
|
|
|
{
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t character = 0;
|
|
|
|
if (hex)
|
|
|
|
{
|
|
|
|
SDL_sscanf(number.c_str(), "%" SCNx32, &character);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
SDL_sscanf(number.c_str(), "%" SCNu32, &character);
|
|
|
|
}
|
|
|
|
uint32_t utf32[] = {character, 0};
|
|
|
|
std::string utf8;
|
|
|
|
utf8::unchecked::utf32to8(utf32, utf32 + 1, std::back_inserter(utf8));
|
|
|
|
value.replace(start_pos, end - start_pos + 1, utf8);
|
|
|
|
}
|
|
|
|
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define TAG_FINDER(NAME, TAG) \
|
|
|
|
static std::string NAME(const std::string& buf) \
|
|
|
|
{ \
|
|
|
|
return find_tag(buf, "<" TAG ">", "</" TAG ">"); \
|
|
|
|
}
|
|
|
|
|
|
|
|
TAG_FINDER(find_metadata, "MetaData") //only for checking that it exists
|
|
|
|
|
|
|
|
TAG_FINDER(find_creator, "Creator")
|
|
|
|
TAG_FINDER(find_title, "Title")
|
|
|
|
TAG_FINDER(find_desc1, "Desc1")
|
|
|
|
TAG_FINDER(find_desc2, "Desc2")
|
|
|
|
TAG_FINDER(find_desc3, "Desc3")
|
|
|
|
TAG_FINDER(find_website, "website")
|
|
|
|
|
2022-06-19 22:41:48 +02:00
|
|
|
/* For CliPlaytestArgs */
|
|
|
|
TAG_FINDER(find_playtest, "Playtest")
|
|
|
|
TAG_FINDER(find_playx, "playx")
|
|
|
|
TAG_FINDER(find_playy, "playy")
|
|
|
|
TAG_FINDER(find_playrx, "playrx")
|
|
|
|
TAG_FINDER(find_playry, "playry")
|
|
|
|
TAG_FINDER(find_playgc, "playgc")
|
|
|
|
TAG_FINDER(find_playmusic, "playmusic")
|
|
|
|
|
2021-02-20 05:51:25 +01:00
|
|
|
#undef TAG_FINDER
|
|
|
|
|
|
|
|
static void levelMetaDataCallback(const char* filename)
|
|
|
|
{
|
2021-02-21 00:40:11 +01:00
|
|
|
extern customlevelclass cl;
|
2021-02-20 05:51:25 +01:00
|
|
|
LevelMetaData temp;
|
|
|
|
std::string filename_ = filename;
|
|
|
|
|
|
|
|
if (!endsWith(filename, ".vvvvvv")
|
|
|
|
|| !FILESYSTEM_isFile(filename)
|
|
|
|
|| FILESYSTEM_isMounted(filename))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
if (cl.getLevelMetaData(filename_, temp))
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
2021-02-21 00:40:11 +01:00
|
|
|
cl.ListOfMetaData.push_back(temp);
|
2021-02-20 05:51:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
void customlevelclass::getDirectoryData(void)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
|
|
|
|
ListOfMetaData.clear();
|
|
|
|
|
|
|
|
FILESYSTEM_clearLevelDirError();
|
|
|
|
|
|
|
|
loadZips();
|
|
|
|
|
|
|
|
FILESYSTEM_enumerateLevelDirFileNames(levelMetaDataCallback);
|
|
|
|
|
|
|
|
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]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2022-06-19 22:41:48 +02:00
|
|
|
bool customlevelclass::getLevelMetaDataAndPlaytestArgs(const std::string& _path, LevelMetaData& _data, CliPlaytestArgs* pt_args)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
unsigned char *uMem;
|
|
|
|
FILESYSTEM_loadFileToMemory(_path.c_str(), &uMem, NULL, true);
|
|
|
|
|
|
|
|
if (uMem == NULL)
|
|
|
|
{
|
|
|
|
vlog_warn("Level %s not found :(", _path.c_str());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string buf((char*) uMem);
|
|
|
|
|
2022-12-01 07:30:16 +01:00
|
|
|
VVV_free(uMem);
|
2021-02-20 05:51:25 +01:00
|
|
|
|
|
|
|
if (find_metadata(buf) == "")
|
|
|
|
{
|
|
|
|
vlog_warn("Couldn't load metadata for %s", _path.c_str());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
_data.creator = find_creator(buf);
|
|
|
|
_data.title = find_title(buf);
|
|
|
|
_data.Desc1 = find_desc1(buf);
|
|
|
|
_data.Desc2 = find_desc2(buf);
|
|
|
|
_data.Desc3 = find_desc3(buf);
|
|
|
|
_data.website = find_website(buf);
|
|
|
|
|
2022-06-19 22:41:48 +02:00
|
|
|
if (pt_args != NULL)
|
|
|
|
{
|
|
|
|
const std::string playtest = find_playtest(buf);
|
|
|
|
if (playtest == "")
|
|
|
|
{
|
|
|
|
pt_args->valid = false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pt_args->valid = true;
|
|
|
|
pt_args->x = help.Int(find_playx(playtest).c_str());
|
|
|
|
pt_args->y = help.Int(find_playy(playtest).c_str());
|
|
|
|
pt_args->rx = help.Int(find_playrx(playtest).c_str());
|
|
|
|
pt_args->ry = help.Int(find_playry(playtest).c_str());
|
|
|
|
pt_args->gc = help.Int(find_playgc(playtest).c_str());
|
|
|
|
pt_args->music = help.Int(find_playmusic(playtest).c_str());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-20 05:51:25 +01:00
|
|
|
_data.filename = _path;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-06-19 22:41:48 +02:00
|
|
|
bool customlevelclass::getLevelMetaData(const std::string& _path, LevelMetaData& _data)
|
|
|
|
{
|
|
|
|
return getLevelMetaDataAndPlaytestArgs(_path, _data, NULL);
|
|
|
|
}
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
void customlevelclass::reset(void)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
version=2; //New smaller format change is 2
|
|
|
|
|
|
|
|
mapwidth=5;
|
|
|
|
mapheight=5;
|
|
|
|
|
2021-02-21 01:04:50 +01:00
|
|
|
title="Untitled Level";
|
|
|
|
creator="Unknown";
|
2021-02-20 05:51:25 +01:00
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
levmusic=0;
|
2021-02-20 05:51:25 +01:00
|
|
|
|
2021-02-21 01:01:39 +01:00
|
|
|
customentities.clear();
|
2021-02-20 05:51:25 +01:00
|
|
|
levmusic=0;
|
|
|
|
|
|
|
|
for (int j = 0; j < maxheight; j++)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < maxwidth; i++)
|
|
|
|
{
|
2021-02-21 00:54:24 +01:00
|
|
|
roomproperties[i+(j*maxwidth)].tileset=0;
|
|
|
|
roomproperties[i+(j*maxwidth)].tilecol=(i+j)%32;
|
|
|
|
roomproperties[i+(j*maxwidth)].roomname="";
|
|
|
|
roomproperties[i+(j*maxwidth)].warpdir=0;
|
|
|
|
roomproperties[i+(j*maxwidth)].platx1=0;
|
|
|
|
roomproperties[i+(j*maxwidth)].platy1=0;
|
|
|
|
roomproperties[i+(j*maxwidth)].platx2=320;
|
|
|
|
roomproperties[i+(j*maxwidth)].platy2=240;
|
|
|
|
roomproperties[i+(j*maxwidth)].platv=4;
|
|
|
|
roomproperties[i+(j*maxwidth)].enemyx1=0;
|
|
|
|
roomproperties[i+(j*maxwidth)].enemyy1=0;
|
|
|
|
roomproperties[i+(j*maxwidth)].enemyx2=320;
|
|
|
|
roomproperties[i+(j*maxwidth)].enemyy2=240;
|
|
|
|
roomproperties[i+(j*maxwidth)].enemytype=0;
|
|
|
|
roomproperties[i+(j*maxwidth)].directmode=0;
|
2021-02-20 05:51:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
SDL_zeroa(contents);
|
|
|
|
|
|
|
|
script.clearcustom();
|
|
|
|
|
|
|
|
onewaycol_override = false;
|
|
|
|
}
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
const int* customlevelclass::loadlevel( int rxi, int ryi )
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
//Set up our buffer array to be picked up by mapclass
|
|
|
|
rxi -= 100;
|
|
|
|
ryi -= 100;
|
|
|
|
if(rxi<0)rxi+=mapwidth;
|
|
|
|
if(ryi<0)ryi+=mapheight;
|
|
|
|
if(rxi>=mapwidth)rxi-=mapwidth;
|
|
|
|
if(ryi>=mapheight)ryi-=mapheight;
|
|
|
|
|
|
|
|
static int result[1200];
|
|
|
|
|
|
|
|
for (int j = 0; j < 30; j++)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < 40; i++)
|
|
|
|
{
|
|
|
|
result[i + j*40] = gettile(rxi, ryi, i, j);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
int customlevelclass::getlevelcol(const int tileset, const int tilecol)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
if(tileset==0) //Space Station
|
|
|
|
{
|
|
|
|
return tilecol;
|
|
|
|
}
|
|
|
|
else if(tileset==1) //Outside
|
|
|
|
{
|
|
|
|
return 32+tilecol;
|
|
|
|
}
|
|
|
|
else if(tileset==2) //Lab
|
|
|
|
{
|
|
|
|
return 40+tilecol;
|
|
|
|
}
|
|
|
|
else if(tileset==3) //Warp Zone
|
|
|
|
{
|
|
|
|
return 46+tilecol;
|
|
|
|
}
|
|
|
|
else if(tileset==4) //Ship
|
|
|
|
{
|
|
|
|
return 52+tilecol;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
int customlevelclass::getenemycol(int t)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
int customlevelclass::getwarpbackground(int rx, int ry)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
2021-02-21 00:45:48 +01:00
|
|
|
const RoomProperty* const room = getroomprop(rx, ry);
|
2021-02-20 05:51:25 +01:00
|
|
|
switch(room->tileset)
|
|
|
|
{
|
|
|
|
case 0: //Space Station
|
|
|
|
switch(room->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(room->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(room->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(room->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(room->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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
int customlevelclass::gettileidx(
|
2021-02-20 05:51:25 +01:00
|
|
|
const int rx,
|
|
|
|
const int ry,
|
|
|
|
const int x,
|
|
|
|
const int y
|
|
|
|
) {
|
|
|
|
const int yoff = y + ry*30;
|
|
|
|
int mult;
|
|
|
|
int idx;
|
|
|
|
|
2021-09-25 01:37:27 +02:00
|
|
|
if (Y_INBOUNDS(yoff))
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
2021-09-25 01:37:27 +02:00
|
|
|
mult = VMULT(yoff);
|
2021-02-20 05:51:25 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mult = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
idx = x + rx*40 + mult;
|
|
|
|
|
|
|
|
return idx;
|
|
|
|
}
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
void customlevelclass::settile(
|
2021-02-20 05:51:25 +01:00
|
|
|
const int rx,
|
|
|
|
const int ry,
|
|
|
|
const int x,
|
|
|
|
const int y,
|
|
|
|
const int t
|
|
|
|
) {
|
|
|
|
const int idx = gettileidx(rx, ry, x, y);
|
|
|
|
|
|
|
|
if (!INBOUNDS_ARR(idx, contents))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
contents[idx] = t;
|
|
|
|
}
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
int customlevelclass::gettile(
|
2021-02-20 05:51:25 +01:00
|
|
|
const int rx,
|
|
|
|
const int ry,
|
|
|
|
const int x,
|
|
|
|
const int y
|
|
|
|
) {
|
|
|
|
const int idx = gettileidx(rx, ry, x, y);
|
|
|
|
|
|
|
|
if (!INBOUNDS_ARR(idx, contents))
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return contents[idx];
|
|
|
|
}
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
int customlevelclass::getabstile(const int x, const int y)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
int idx;
|
|
|
|
int yoff;
|
|
|
|
|
2021-09-25 01:37:27 +02:00
|
|
|
if (Y_INBOUNDS(y))
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
2021-09-25 01:37:27 +02:00
|
|
|
yoff = VMULT(y);
|
2021-02-20 05:51:25 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
yoff = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
idx = x + yoff;
|
|
|
|
|
|
|
|
if (!INBOUNDS_ARR(idx, contents))
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return contents[idx];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
int customlevelclass::getroompropidx(const int rx, const int ry)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
return rx + ry*maxwidth;
|
|
|
|
}
|
|
|
|
|
2021-02-21 00:45:48 +01:00
|
|
|
const RoomProperty* customlevelclass::getroomprop(const int rx, const int ry)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
const int idx = getroompropidx(rx, ry);
|
|
|
|
|
2021-02-21 00:54:24 +01:00
|
|
|
if (INBOUNDS_ARR(idx, roomproperties))
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
2021-02-21 00:54:24 +01:00
|
|
|
return &roomproperties[idx];
|
2021-02-20 05:51:25 +01:00
|
|
|
}
|
|
|
|
|
2021-02-21 00:45:48 +01:00
|
|
|
static RoomProperty blank;
|
2021-02-20 05:51:25 +01:00
|
|
|
blank.tileset = 1;
|
|
|
|
blank.directmode = 1;
|
|
|
|
blank.roomname.clear();
|
|
|
|
|
|
|
|
return ␣
|
|
|
|
}
|
|
|
|
|
|
|
|
#define FOREACH_PROP(NAME, TYPE) \
|
2021-02-21 00:40:11 +01:00
|
|
|
void customlevelclass::setroom##NAME(const int rx, const int ry, const TYPE NAME) \
|
2021-02-20 05:51:25 +01:00
|
|
|
{ \
|
|
|
|
const int idx = getroompropidx(rx, ry); \
|
|
|
|
\
|
2021-02-21 00:54:24 +01:00
|
|
|
if (!INBOUNDS_ARR(idx, roomproperties)) \
|
2021-02-20 05:51:25 +01:00
|
|
|
{ \
|
|
|
|
return; \
|
|
|
|
} \
|
|
|
|
\
|
2021-02-21 00:54:24 +01:00
|
|
|
roomproperties[idx].NAME = NAME; \
|
2021-02-20 05:51:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ROOM_PROPERTIES
|
|
|
|
|
|
|
|
#undef FOREACH_PROP
|
|
|
|
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
int customlevelclass::absfree( int x, int y )
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
//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(getabstile(x, y)==0)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(getabstile(x, y)>=2 && getabstile(x, y)<80)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if(getabstile(x, y)>=680)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
void customlevelclass::findstartpoint(void)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
//Ok! Scan the room for the closest checkpoint
|
|
|
|
int testeditor=-1;
|
|
|
|
//First up; is there a start point on this screen?
|
2021-02-21 01:01:39 +01:00
|
|
|
for(size_t i=0; i<customentities.size(); i++)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
//if() on screen
|
2021-02-21 01:01:39 +01:00
|
|
|
if(customentities[i].t==16 && testeditor==-1)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
testeditor=i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(testeditor==-1)
|
|
|
|
{
|
|
|
|
game.edsavex = 160;
|
|
|
|
game.edsavey = 120;
|
|
|
|
game.edsaverx = 100;
|
|
|
|
game.edsavery = 100;
|
|
|
|
game.edsavegc = 0;
|
|
|
|
game.edsavey--;
|
|
|
|
game.edsavedir=1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//Start point spawn
|
Remove overcomplicated integer divisions
Believe it or not, there are still some remnants of the ActionScript
coding standards in the codebase! And one of them sometimes pops up
whenever an integer division happens.
As it so happens, it seems like division in ActionScript automatically
produces a decimal number. So to prevent that, the game sometimes
subtracts off the remainder of the number to be divided before
performing the division on it.
Thus, we get statements that look like
(a - (a % b)) / b
And probably more parentheses surrounding it too, since it would be
copy-pasted into yet another larger expression, because of course it
would.
`(a % b)` here is subtracting the remainder of `a` divided by `b`, using
the modulo operator, before it gets divided by `b`. Thus, the number
will always be divisible by `b`, so dividing it will mathematically not
produce a decimal number.
Needless to say, this is unnecessary, and very unreadable. In fact, when
I saw these for the first time, I thought they were overcomplicated
_modulos_, _not_ integer division! In C and C++, dividing an integer by
an integer will always result in an integer, so there's no need to do
all this runaround just to divide two integers.
To find all of these, I used the command
rg --pcre2 '(.+?).+?-.+?(?=\1).+?%.+?([\d]+?).+?\/.+?(?=\2)'
which basically matches expressions of the form 'a - a % b / b', where
'a' and 'b' are identical and there could be any characters in the
spaces.
2021-09-25 02:21:46 +02:00
|
|
|
int tx=customentities[testeditor].x/40;
|
|
|
|
int ty=customentities[testeditor].y/30;
|
2021-02-21 01:01:39 +01:00
|
|
|
game.edsavex = ((customentities[testeditor].x%40)*8)-4;
|
|
|
|
game.edsavey = (customentities[testeditor].y%30)*8;
|
2021-02-20 05:51:25 +01:00
|
|
|
game.edsaverx = 100+tx;
|
|
|
|
game.edsavery = 100+ty;
|
|
|
|
game.edsavegc = 0;
|
|
|
|
game.edsavey++;
|
2021-02-21 01:01:39 +01:00
|
|
|
game.edsavedir=1-customentities[testeditor].p1;
|
2021-02-20 05:51:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
int customlevelclass::findtrinket(int t)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
int ttrinket=0;
|
2021-02-21 01:01:39 +01:00
|
|
|
for(int i=0; i<(int)customentities.size(); i++)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
if(i==t) return ttrinket;
|
2021-02-21 01:01:39 +01:00
|
|
|
if(customentities[i].t==9) ttrinket++;
|
2021-02-20 05:51:25 +01:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
int customlevelclass::findcrewmate(int t)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
int ttrinket=0;
|
2021-02-21 01:01:39 +01:00
|
|
|
for(int i=0; i<(int)customentities.size(); i++)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
if(i==t) return ttrinket;
|
2021-02-21 01:01:39 +01:00
|
|
|
if(customentities[i].t==15) ttrinket++;
|
2021-02-20 05:51:25 +01:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
int customlevelclass::findwarptoken(int t)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
int ttrinket=0;
|
2021-02-21 01:01:39 +01:00
|
|
|
for(int i=0; i<(int)customentities.size(); i++)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
if(i==t) return ttrinket;
|
2021-02-21 01:01:39 +01:00
|
|
|
if(customentities[i].t==13) ttrinket++;
|
2021-02-20 05:51:25 +01:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
bool customlevelclass::load(std::string& _path)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
tinyxml2::XMLDocument doc;
|
|
|
|
tinyxml2::XMLHandle hDoc(&doc);
|
|
|
|
tinyxml2::XMLElement* pElem;
|
|
|
|
|
|
|
|
reset();
|
2021-02-21 00:40:11 +01:00
|
|
|
#ifndef NO_EDITOR
|
|
|
|
ed.reset();
|
|
|
|
#endif
|
2021-02-20 05:51:25 +01:00
|
|
|
|
|
|
|
static const char *levelDir = "levels/";
|
|
|
|
if (_path.compare(0, SDL_strlen(levelDir), levelDir) != 0)
|
|
|
|
{
|
|
|
|
_path = levelDir + _path;
|
|
|
|
}
|
|
|
|
|
|
|
|
FILESYSTEM_unmountAssets();
|
|
|
|
if (game.cliplaytest && game.playassets != "")
|
|
|
|
{
|
|
|
|
MAYBE_FAIL(FILESYSTEM_mountAssets(game.playassets.c_str()));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
MAYBE_FAIL(FILESYSTEM_mountAssets(_path.c_str()));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!FILESYSTEM_loadTiXml2Document(_path.c_str(), doc))
|
|
|
|
{
|
|
|
|
vlog_warn("%s not found", _path.c_str());
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (doc.Error())
|
|
|
|
{
|
|
|
|
vlog_error("Error parsing %s: %s", _path.c_str(), doc.ErrorStr());
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
#ifndef NO_EDITOR
|
|
|
|
ed.loaded_filepath = _path;
|
|
|
|
#endif
|
2021-02-20 05:51:25 +01:00
|
|
|
|
|
|
|
version = 0;
|
|
|
|
|
|
|
|
for (pElem = hDoc
|
|
|
|
.FirstChildElement()
|
|
|
|
.FirstChildElement("Data")
|
|
|
|
.FirstChildElement()
|
|
|
|
.ToElement();
|
|
|
|
pElem != NULL;
|
|
|
|
pElem = pElem->NextSiblingElement())
|
|
|
|
{
|
|
|
|
const char* pKey = pElem->Value();
|
|
|
|
const char* pText = pElem->GetText();
|
|
|
|
if(pText == NULL)
|
|
|
|
{
|
|
|
|
pText = "";
|
|
|
|
}
|
|
|
|
|
|
|
|
if (SDL_strcmp(pKey, "MetaData") == 0)
|
|
|
|
{
|
|
|
|
|
|
|
|
for( tinyxml2::XMLElement* subElem = pElem->FirstChildElement(); subElem; subElem= subElem->NextSiblingElement())
|
|
|
|
{
|
|
|
|
const char* pKey_ = subElem->Value();
|
|
|
|
const char* pText_ = subElem->GetText() ;
|
|
|
|
if(pText_ == NULL)
|
|
|
|
{
|
|
|
|
pText_ = "";
|
|
|
|
}
|
|
|
|
|
|
|
|
if(SDL_strcmp(pKey_, "Creator") == 0)
|
|
|
|
{
|
2021-02-21 01:04:50 +01:00
|
|
|
creator = pText_;
|
2021-02-20 05:51:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if(SDL_strcmp(pKey_, "Title") == 0)
|
|
|
|
{
|
2021-02-21 01:04:50 +01:00
|
|
|
title = pText_;
|
2021-02-20 05:51:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if(SDL_strcmp(pKey_, "Desc1") == 0)
|
|
|
|
{
|
|
|
|
Desc1 = pText_;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(SDL_strcmp(pKey_, "Desc2") == 0)
|
|
|
|
{
|
|
|
|
Desc2 = pText_;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(SDL_strcmp(pKey_, "Desc3") == 0)
|
|
|
|
{
|
|
|
|
Desc3 = pText_;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(SDL_strcmp(pKey_, "website") == 0)
|
|
|
|
{
|
|
|
|
website = pText_;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(SDL_strcmp(pKey_, "onewaycol_override") == 0)
|
|
|
|
{
|
|
|
|
onewaycol_override = help.Int(pText_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (SDL_strcmp(pKey, "mapwidth") == 0)
|
|
|
|
{
|
|
|
|
mapwidth = help.Int(pText);
|
|
|
|
}
|
|
|
|
if (SDL_strcmp(pKey, "mapheight") == 0)
|
|
|
|
{
|
|
|
|
mapheight = help.Int(pText);
|
|
|
|
}
|
|
|
|
if (SDL_strcmp(pKey, "levmusic") == 0)
|
|
|
|
{
|
|
|
|
levmusic = help.Int(pText);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (SDL_strcmp(pKey, "contents") == 0 && pText[0] != '\0')
|
|
|
|
{
|
|
|
|
int x = 0;
|
|
|
|
int y = 0;
|
|
|
|
|
|
|
|
char buffer[16];
|
|
|
|
size_t start = 0;
|
|
|
|
|
|
|
|
while (next_split_s(buffer, sizeof(buffer), &start, pText, ','))
|
|
|
|
{
|
|
|
|
const int idx = x + maxwidth*40*y;
|
|
|
|
|
|
|
|
if (INBOUNDS_ARR(idx, contents))
|
|
|
|
{
|
|
|
|
contents[idx] = help.Int(buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
++x;
|
|
|
|
|
|
|
|
if (x == mapwidth*40)
|
|
|
|
{
|
|
|
|
x = 0;
|
|
|
|
++y;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (SDL_strcmp(pKey, "edEntities") == 0)
|
|
|
|
{
|
|
|
|
for( tinyxml2::XMLElement* edEntityEl = pElem->FirstChildElement(); edEntityEl; edEntityEl=edEntityEl->NextSiblingElement())
|
|
|
|
{
|
2021-02-21 00:56:59 +01:00
|
|
|
CustomEntity entity = CustomEntity();
|
2021-02-20 05:51:25 +01:00
|
|
|
const char* text = edEntityEl->GetText();
|
|
|
|
|
|
|
|
if (text != NULL)
|
|
|
|
{
|
|
|
|
size_t len = SDL_strlen(text);
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
|
|
|
|
if (endsWith(text, "\n ")) // linefeed + exactly 12 spaces
|
|
|
|
{
|
|
|
|
// 12 spaces + 1 linefeed = 13 chars
|
|
|
|
len -= 13;
|
|
|
|
}
|
|
|
|
|
|
|
|
entity.scriptname = std::string(text, len);
|
|
|
|
}
|
|
|
|
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);
|
|
|
|
|
2021-02-21 01:01:39 +01:00
|
|
|
customentities.push_back(entity);
|
2021-02-20 05:51:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (SDL_strcmp(pKey, "levelMetaData") == 0)
|
|
|
|
{
|
|
|
|
int i = 0;
|
|
|
|
for( tinyxml2::XMLElement* edLevelClassElement = pElem->FirstChildElement(); edLevelClassElement; edLevelClassElement=edLevelClassElement->NextSiblingElement())
|
|
|
|
{
|
2021-02-21 00:54:24 +01:00
|
|
|
if (!INBOUNDS_ARR(i, roomproperties))
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(edLevelClassElement->GetText() != NULL)
|
|
|
|
{
|
2021-02-21 00:54:24 +01:00
|
|
|
roomproperties[i].roomname = std::string(edLevelClassElement->GetText()) ;
|
2021-02-20 05:51:25 +01:00
|
|
|
}
|
|
|
|
|
2021-02-21 00:54:24 +01:00
|
|
|
edLevelClassElement->QueryIntAttribute("tileset", &roomproperties[i].tileset);
|
|
|
|
edLevelClassElement->QueryIntAttribute("tilecol", &roomproperties[i].tilecol);
|
|
|
|
edLevelClassElement->QueryIntAttribute("platx1", &roomproperties[i].platx1);
|
|
|
|
edLevelClassElement->QueryIntAttribute("platy1", &roomproperties[i].platy1);
|
|
|
|
edLevelClassElement->QueryIntAttribute("platx2", &roomproperties[i].platx2);
|
|
|
|
edLevelClassElement->QueryIntAttribute("platy2", &roomproperties[i].platy2);
|
|
|
|
edLevelClassElement->QueryIntAttribute("platv", &roomproperties[i].platv);
|
|
|
|
edLevelClassElement->QueryIntAttribute("enemyx1", &roomproperties[i].enemyx1);
|
|
|
|
edLevelClassElement->QueryIntAttribute("enemyy1", &roomproperties[i].enemyy1);
|
|
|
|
edLevelClassElement->QueryIntAttribute("enemyx2", &roomproperties[i].enemyx2);
|
|
|
|
edLevelClassElement->QueryIntAttribute("enemyy2", &roomproperties[i].enemyy2);
|
|
|
|
edLevelClassElement->QueryIntAttribute("enemytype", &roomproperties[i].enemytype);
|
|
|
|
edLevelClassElement->QueryIntAttribute("directmode", &roomproperties[i].directmode);
|
|
|
|
|
|
|
|
edLevelClassElement->QueryIntAttribute("warpdir", &roomproperties[i].warpdir);
|
2021-02-20 05:51:25 +01:00
|
|
|
|
|
|
|
i++;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (SDL_strcmp(pKey, "script") == 0 && pText[0] != '\0')
|
|
|
|
{
|
|
|
|
Script script_;
|
|
|
|
bool headerfound = false;
|
|
|
|
|
|
|
|
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] == ':')
|
|
|
|
{
|
|
|
|
if (headerfound)
|
|
|
|
{
|
|
|
|
script.customscripts.push_back(script_);
|
|
|
|
}
|
|
|
|
|
|
|
|
script_.name = std::string(&pText[prev_start], len - 1);
|
|
|
|
script_.contents.clear();
|
|
|
|
headerfound = true;
|
|
|
|
|
|
|
|
goto next;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (headerfound)
|
|
|
|
{
|
|
|
|
script_.contents.push_back(std::string(&pText[prev_start], len));
|
|
|
|
}
|
|
|
|
|
|
|
|
next:
|
|
|
|
prev_start = start;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Add the last script */
|
|
|
|
if (headerfound)
|
|
|
|
{
|
|
|
|
script.customscripts.push_back(script_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mapwidth < maxwidth)
|
|
|
|
{
|
|
|
|
/* Unscramble platv, since it was stored incorrectly
|
|
|
|
* in 2.2 and previous... */
|
|
|
|
size_t i;
|
|
|
|
int x = 0;
|
|
|
|
int y = 0;
|
|
|
|
int temp_platv[numrooms];
|
|
|
|
|
|
|
|
for (i = 0; i < numrooms; ++i)
|
|
|
|
{
|
2021-02-21 00:54:24 +01:00
|
|
|
temp_platv[i] = roomproperties[i].platv;
|
2021-02-20 05:51:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < numrooms; ++i)
|
|
|
|
{
|
|
|
|
if (x < mapwidth)
|
|
|
|
{
|
|
|
|
const int platv_idx = x + y * mapwidth;
|
|
|
|
if (INBOUNDS_ARR(platv_idx, temp_platv))
|
|
|
|
{
|
2021-02-21 00:54:24 +01:00
|
|
|
roomproperties[i].platv = temp_platv[platv_idx];
|
2021-02-20 05:51:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-02-21 00:54:24 +01:00
|
|
|
roomproperties[i].platv = 4; /* default */
|
2021-02-20 05:51:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
++x;
|
|
|
|
|
|
|
|
if (x >= maxwidth)
|
|
|
|
{
|
|
|
|
x = 0;
|
|
|
|
++y;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
#ifndef NO_EDITOR
|
|
|
|
ed.gethooks();
|
|
|
|
#endif
|
|
|
|
|
2021-02-20 05:51:25 +01:00
|
|
|
version=2;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
fail:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
#ifndef NO_EDITOR
|
2021-09-07 00:41:49 +02:00
|
|
|
bool customlevelclass::save(const std::string& _path)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
tinyxml2::XMLDocument doc;
|
|
|
|
|
|
|
|
std::string newpath("levels/" + _path);
|
|
|
|
|
|
|
|
// Try to preserve the XML of the currently-loaded one
|
2021-02-21 00:40:11 +01:00
|
|
|
bool already_exists = !ed.loaded_filepath.empty()
|
|
|
|
&& FILESYSTEM_loadTiXml2Document(ed.loaded_filepath.c_str(), doc);
|
|
|
|
if (!already_exists && !ed.loaded_filepath.empty())
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
2021-02-21 00:40:11 +01:00
|
|
|
vlog_error("Currently-loaded %s not found", ed.loaded_filepath.c_str());
|
2021-02-20 05:51:25 +01:00
|
|
|
}
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
ed.loaded_filepath = newpath;
|
2021-02-20 05:51:25 +01:00
|
|
|
|
|
|
|
tinyxml2::XMLElement* msg;
|
|
|
|
|
|
|
|
xml::update_declaration(doc);
|
|
|
|
|
|
|
|
tinyxml2::XMLElement * root = xml::update_element(doc, "MapData");
|
|
|
|
root->SetAttribute("version",version);
|
|
|
|
|
|
|
|
xml::update_comment(root, " Save file ");
|
|
|
|
|
|
|
|
tinyxml2::XMLElement * data = xml::update_element(root, "Data");
|
|
|
|
|
|
|
|
msg = xml::update_element(data, "MetaData");
|
|
|
|
|
|
|
|
//getUser
|
2021-02-21 01:04:50 +01:00
|
|
|
xml::update_tag(msg, "Creator", creator.c_str());
|
2021-02-20 05:51:25 +01:00
|
|
|
|
2021-02-21 01:04:50 +01:00
|
|
|
xml::update_tag(msg, "Title", title.c_str());
|
2021-02-20 05:51:25 +01:00
|
|
|
|
|
|
|
xml::update_tag(msg, "Created", version);
|
|
|
|
|
2021-02-21 01:04:50 +01:00
|
|
|
xml::update_tag(msg, "Modified", modifier.c_str());
|
2021-02-20 05:51:25 +01:00
|
|
|
|
|
|
|
xml::update_tag(msg, "Modifiers", version);
|
|
|
|
|
|
|
|
xml::update_tag(msg, "Desc1", Desc1.c_str());
|
|
|
|
|
|
|
|
xml::update_tag(msg, "Desc2", Desc2.c_str());
|
|
|
|
|
|
|
|
xml::update_tag(msg, "Desc3", Desc3.c_str());
|
|
|
|
|
|
|
|
xml::update_tag(msg, "website", website.c_str());
|
|
|
|
|
|
|
|
if (onewaycol_override)
|
|
|
|
{
|
|
|
|
xml::update_tag(msg, "onewaycol_override", onewaycol_override);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Delete the element. I could just delete one, but just to be sure,
|
|
|
|
// I will delete all of them if there are more than one
|
|
|
|
tinyxml2::XMLElement* element;
|
|
|
|
while ((element = msg->FirstChildElement("onewaycol_override"))
|
|
|
|
!= NULL)
|
|
|
|
{
|
|
|
|
doc.DeleteNode(element);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
xml::update_tag(data, "mapwidth", mapwidth);
|
|
|
|
|
|
|
|
xml::update_tag(data, "mapheight", mapheight);
|
|
|
|
|
|
|
|
xml::update_tag(data, "levmusic", levmusic);
|
|
|
|
|
|
|
|
//New save format
|
|
|
|
std::string contentsString="";
|
|
|
|
for(int y = 0; y < mapheight*30; y++ )
|
|
|
|
{
|
|
|
|
for(int x = 0; x < mapwidth*40; x++ )
|
|
|
|
{
|
|
|
|
contentsString += help.String(getabstile(x, y)) + ",";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
xml::update_tag(data, "contents", contentsString.c_str());
|
|
|
|
|
|
|
|
|
|
|
|
msg = xml::update_element_delete_contents(data, "edEntities");
|
2021-02-21 01:01:39 +01:00
|
|
|
for(size_t i = 0; i < customentities.size(); i++)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
tinyxml2::XMLElement *edentityElement = doc.NewElement( "edentity" );
|
2021-02-21 01:01:39 +01:00
|
|
|
edentityElement->SetAttribute( "x", customentities[i].x);
|
|
|
|
edentityElement->SetAttribute( "y", customentities[i].y);
|
|
|
|
edentityElement->SetAttribute( "t", customentities[i].t);
|
|
|
|
edentityElement->SetAttribute( "p1", customentities[i].p1);
|
|
|
|
edentityElement->SetAttribute( "p2", customentities[i].p2);
|
|
|
|
edentityElement->SetAttribute( "p3", customentities[i].p3);
|
|
|
|
edentityElement->SetAttribute( "p4", customentities[i].p4);
|
|
|
|
edentityElement->SetAttribute( "p5", customentities[i].p5);
|
|
|
|
edentityElement->SetAttribute( "p6", customentities[i].p6);
|
|
|
|
edentityElement->LinkEndChild( doc.NewText( customentities[i].scriptname.c_str() )) ;
|
2021-02-20 05:51:25 +01:00
|
|
|
msg->LinkEndChild( edentityElement );
|
|
|
|
}
|
|
|
|
|
|
|
|
msg = xml::update_element_delete_contents(data, "levelMetaData");
|
|
|
|
|
|
|
|
int temp_platv[numrooms];
|
2021-09-11 01:55:27 +02:00
|
|
|
for (size_t i = 0; i < SDL_arraysize(temp_platv); ++i)
|
|
|
|
{
|
|
|
|
temp_platv[i] = 4; /* default */
|
|
|
|
}
|
2021-02-20 05:51:25 +01:00
|
|
|
|
|
|
|
if (mapwidth < maxwidth)
|
|
|
|
{
|
|
|
|
/* Re-scramble platv, since it was stored incorrectly
|
|
|
|
* in 2.2 and previous... */
|
|
|
|
size_t i;
|
|
|
|
int x = 0;
|
|
|
|
int y = 0;
|
|
|
|
for (i = 0; i < numrooms; ++i)
|
|
|
|
{
|
|
|
|
if (x < mapwidth)
|
|
|
|
{
|
|
|
|
const int platv_idx = x + y * mapwidth;
|
|
|
|
if (INBOUNDS_ARR(platv_idx, temp_platv))
|
|
|
|
{
|
2021-02-21 00:54:24 +01:00
|
|
|
temp_platv[platv_idx] = roomproperties[i].platv;
|
2021-02-20 05:51:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
++x;
|
|
|
|
|
|
|
|
if (x >= mapwidth)
|
|
|
|
{
|
|
|
|
/* Skip to next actual row. */
|
|
|
|
i += maxwidth - mapwidth;
|
|
|
|
x = 0;
|
|
|
|
++y;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-21 00:54:24 +01:00
|
|
|
for(size_t i = 0; i < SDL_arraysize(roomproperties); i++)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
2021-02-21 00:45:48 +01:00
|
|
|
tinyxml2::XMLElement *roompropertyElement = doc.NewElement( "edLevelClass" );
|
2021-02-21 00:54:24 +01:00
|
|
|
roompropertyElement->SetAttribute( "tileset", roomproperties[i].tileset);
|
|
|
|
roompropertyElement->SetAttribute( "tilecol", roomproperties[i].tilecol);
|
|
|
|
roompropertyElement->SetAttribute( "platx1", roomproperties[i].platx1);
|
|
|
|
roompropertyElement->SetAttribute( "platy1", roomproperties[i].platy1);
|
|
|
|
roompropertyElement->SetAttribute( "platx2", roomproperties[i].platx2);
|
|
|
|
roompropertyElement->SetAttribute( "platy2", roomproperties[i].platy2);
|
2021-02-21 00:45:48 +01:00
|
|
|
roompropertyElement->SetAttribute( "platv", temp_platv[i]);
|
2021-02-21 00:54:24 +01:00
|
|
|
roompropertyElement->SetAttribute( "enemyx1", roomproperties[i].enemyx1);
|
|
|
|
roompropertyElement->SetAttribute( "enemyy1", roomproperties[i].enemyy1);
|
|
|
|
roompropertyElement->SetAttribute( "enemyx2", roomproperties[i].enemyx2);
|
|
|
|
roompropertyElement->SetAttribute( "enemyy2", roomproperties[i].enemyy2);
|
|
|
|
roompropertyElement->SetAttribute( "enemytype", roomproperties[i].enemytype);
|
|
|
|
roompropertyElement->SetAttribute( "directmode", roomproperties[i].directmode);
|
|
|
|
roompropertyElement->SetAttribute( "warpdir", roomproperties[i].warpdir);
|
|
|
|
|
|
|
|
roompropertyElement->LinkEndChild( doc.NewText( roomproperties[i].roomname.c_str() )) ;
|
2021-02-21 00:45:48 +01:00
|
|
|
msg->LinkEndChild( roompropertyElement );
|
2021-02-20 05:51:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string scriptString;
|
|
|
|
for(size_t i = 0; i < script.customscripts.size(); i++)
|
|
|
|
{
|
|
|
|
Script& script_ = script.customscripts[i];
|
|
|
|
|
|
|
|
scriptString += script_.name + ":|";
|
|
|
|
for (size_t ii = 0; ii < script_.contents.size(); ++ii)
|
|
|
|
{
|
|
|
|
scriptString += script_.contents[ii];
|
|
|
|
|
|
|
|
// Inserts a space if the line ends with a :
|
|
|
|
if (script_.contents[ii].length() && *script_.contents[ii].rbegin() == ':')
|
|
|
|
{
|
|
|
|
scriptString += " ";
|
|
|
|
}
|
|
|
|
|
|
|
|
scriptString += "|";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
xml::update_tag(data, "script", scriptString.c_str());
|
|
|
|
|
|
|
|
return FILESYSTEM_saveTiXml2Document(newpath.c_str(), doc);
|
|
|
|
}
|
2021-02-21 00:40:11 +01:00
|
|
|
#endif /* NO_EDITOR */
|
2021-02-20 05:51:25 +01:00
|
|
|
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
void customlevelclass::generatecustomminimap(void)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
2022-11-30 22:25:28 +01:00
|
|
|
map.customzoom = 1;
|
Unify all queries to map size to `map.getwidth` and `map.getheight`
It's becoming pretty clear that the size of the map is important enough
to be queried a lot, but each time it's something like `map.custommode ?
map.customwidth : 20` and `map.custommode ? map.customheight : 20` which
is not ideal because of copy-pasting.
Furthermore, even `map.customwidth` and `map.customheight` are just
duplicates of `cl.mapwidth` and `cl.mapheight`, which are only set in
`customlevelclass::generatecustomminimap`. This is a bit annoying if you
want to, say, add checks that depend on the width and height of the
custom map in `mapclass::initcustommapdata`, but `map.customwidth` and
`map.customheight` are out of date because `generatecustomminimap`
hasn't been called yet. And doing the ternary there requires a `#ifndef
NO_CUSTOM_LEVELS` to reference `cl.mapwidth` and `cl.mapheight` which is
just awful.
So I'm axing `map.customwidth` and `map.customheight`, and I'm axing all
the ternaries that are duplicating the source of truth in
`MapRenderData`. Instead, there will just be one function to call for
the width and height, `mapclass::getwidth` and `mapclass::getheight`,
and everyone can simply call those without needing to do ternaries or
duplication.
2022-11-30 22:35:14 +01:00
|
|
|
if (mapwidth <= 10 && mapheight <= 10)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
2022-11-30 22:25:28 +01:00
|
|
|
map.customzoom = 2;
|
2021-02-20 05:51:25 +01:00
|
|
|
}
|
Unify all queries to map size to `map.getwidth` and `map.getheight`
It's becoming pretty clear that the size of the map is important enough
to be queried a lot, but each time it's something like `map.custommode ?
map.customwidth : 20` and `map.custommode ? map.customheight : 20` which
is not ideal because of copy-pasting.
Furthermore, even `map.customwidth` and `map.customheight` are just
duplicates of `cl.mapwidth` and `cl.mapheight`, which are only set in
`customlevelclass::generatecustomminimap`. This is a bit annoying if you
want to, say, add checks that depend on the width and height of the
custom map in `mapclass::initcustommapdata`, but `map.customwidth` and
`map.customheight` are out of date because `generatecustomminimap`
hasn't been called yet. And doing the ternary there requires a `#ifndef
NO_CUSTOM_LEVELS` to reference `cl.mapwidth` and `cl.mapheight` which is
just awful.
So I'm axing `map.customwidth` and `map.customheight`, and I'm axing all
the ternaries that are duplicating the source of truth in
`MapRenderData`. Instead, there will just be one function to call for
the width and height, `mapclass::getwidth` and `mapclass::getheight`,
and everyone can simply call those without needing to do ternaries or
duplication.
2022-11-30 22:35:14 +01:00
|
|
|
if (mapwidth <= 5 && mapheight <= 5)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
2022-11-30 22:25:28 +01:00
|
|
|
map.customzoom = 4;
|
2021-02-20 05:51:25 +01:00
|
|
|
}
|
|
|
|
|
2022-11-30 22:25:28 +01:00
|
|
|
// Set minimap offsets
|
|
|
|
switch (map.customzoom)
|
|
|
|
{
|
|
|
|
case 4:
|
Unify all queries to map size to `map.getwidth` and `map.getheight`
It's becoming pretty clear that the size of the map is important enough
to be queried a lot, but each time it's something like `map.custommode ?
map.customwidth : 20` and `map.custommode ? map.customheight : 20` which
is not ideal because of copy-pasting.
Furthermore, even `map.customwidth` and `map.customheight` are just
duplicates of `cl.mapwidth` and `cl.mapheight`, which are only set in
`customlevelclass::generatecustomminimap`. This is a bit annoying if you
want to, say, add checks that depend on the width and height of the
custom map in `mapclass::initcustommapdata`, but `map.customwidth` and
`map.customheight` are out of date because `generatecustomminimap`
hasn't been called yet. And doing the ternary there requires a `#ifndef
NO_CUSTOM_LEVELS` to reference `cl.mapwidth` and `cl.mapheight` which is
just awful.
So I'm axing `map.customwidth` and `map.customheight`, and I'm axing all
the ternaries that are duplicating the source of truth in
`MapRenderData`. Instead, there will just be one function to call for
the width and height, `mapclass::getwidth` and `mapclass::getheight`,
and everyone can simply call those without needing to do ternaries or
duplication.
2022-11-30 22:35:14 +01:00
|
|
|
map.custommmxoff = 24 * (5 - mapwidth);
|
|
|
|
map.custommmyoff = 18 * (5 - mapheight);
|
2022-11-30 22:25:28 +01:00
|
|
|
break;
|
|
|
|
case 2:
|
Unify all queries to map size to `map.getwidth` and `map.getheight`
It's becoming pretty clear that the size of the map is important enough
to be queried a lot, but each time it's something like `map.custommode ?
map.customwidth : 20` and `map.custommode ? map.customheight : 20` which
is not ideal because of copy-pasting.
Furthermore, even `map.customwidth` and `map.customheight` are just
duplicates of `cl.mapwidth` and `cl.mapheight`, which are only set in
`customlevelclass::generatecustomminimap`. This is a bit annoying if you
want to, say, add checks that depend on the width and height of the
custom map in `mapclass::initcustommapdata`, but `map.customwidth` and
`map.customheight` are out of date because `generatecustomminimap`
hasn't been called yet. And doing the ternary there requires a `#ifndef
NO_CUSTOM_LEVELS` to reference `cl.mapwidth` and `cl.mapheight` which is
just awful.
So I'm axing `map.customwidth` and `map.customheight`, and I'm axing all
the ternaries that are duplicating the source of truth in
`MapRenderData`. Instead, there will just be one function to call for
the width and height, `mapclass::getwidth` and `mapclass::getheight`,
and everyone can simply call those without needing to do ternaries or
duplication.
2022-11-30 22:35:14 +01:00
|
|
|
map.custommmxoff = 12 * (10 - mapwidth);
|
|
|
|
map.custommmyoff = 9 * (10 - mapheight);
|
2022-11-30 22:25:28 +01:00
|
|
|
break;
|
|
|
|
default:
|
Unify all queries to map size to `map.getwidth` and `map.getheight`
It's becoming pretty clear that the size of the map is important enough
to be queried a lot, but each time it's something like `map.custommode ?
map.customwidth : 20` and `map.custommode ? map.customheight : 20` which
is not ideal because of copy-pasting.
Furthermore, even `map.customwidth` and `map.customheight` are just
duplicates of `cl.mapwidth` and `cl.mapheight`, which are only set in
`customlevelclass::generatecustomminimap`. This is a bit annoying if you
want to, say, add checks that depend on the width and height of the
custom map in `mapclass::initcustommapdata`, but `map.customwidth` and
`map.customheight` are out of date because `generatecustomminimap`
hasn't been called yet. And doing the ternary there requires a `#ifndef
NO_CUSTOM_LEVELS` to reference `cl.mapwidth` and `cl.mapheight` which is
just awful.
So I'm axing `map.customwidth` and `map.customheight`, and I'm axing all
the ternaries that are duplicating the source of truth in
`MapRenderData`. Instead, there will just be one function to call for
the width and height, `mapclass::getwidth` and `mapclass::getheight`,
and everyone can simply call those without needing to do ternaries or
duplication.
2022-11-30 22:35:14 +01:00
|
|
|
map.custommmxoff = 6 * (20 - mapwidth);
|
|
|
|
map.custommmyoff = int(4.5 * (20 - mapheight));
|
2022-11-30 22:25:28 +01:00
|
|
|
break;
|
2021-02-20 05:51:25 +01:00
|
|
|
}
|
|
|
|
|
2022-11-30 22:25:28 +01:00
|
|
|
map.custommmxsize = 240 - (map.custommmxoff * 2);
|
|
|
|
map.custommmysize = 180 - (map.custommmyoff * 2);
|
|
|
|
|
2021-02-20 05:51:25 +01:00
|
|
|
FillRect(graphics.images[12], graphics.getRGB(0, 0, 0));
|
|
|
|
|
2022-11-30 22:25:28 +01:00
|
|
|
// Scan over the map size
|
|
|
|
for (int j2 = 0; j2 < mapheight; j2++)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
2022-11-30 22:25:28 +01:00
|
|
|
for (int i2 = 0; i2 < mapwidth; i2++)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
2022-11-30 22:25:28 +01:00
|
|
|
int tm;
|
|
|
|
if (getroomprop(i2, j2)->tileset == 1)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
2022-11-30 22:25:28 +01:00
|
|
|
tm = 96;
|
2021-02-20 05:51:25 +01:00
|
|
|
}
|
2022-11-30 22:25:28 +01:00
|
|
|
else
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
2022-11-30 22:25:28 +01:00
|
|
|
tm = 196;
|
|
|
|
}
|
2021-02-20 05:51:25 +01:00
|
|
|
|
2022-11-30 22:25:28 +01:00
|
|
|
// Ok, now scan over each square
|
|
|
|
for (int j = 0; j < 9 * map.customzoom; j++)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < 12 * map.customzoom; i++)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
2022-11-30 22:25:28 +01:00
|
|
|
int tile;
|
|
|
|
switch (map.customzoom)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
2022-11-30 22:25:28 +01:00
|
|
|
case 4:
|
|
|
|
tile = absfree(
|
|
|
|
int(i * 0.83) + (i2 * 40),
|
|
|
|
int(j * 0.83) + (j2 * 30)
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
tile = absfree(
|
|
|
|
int(i * 1.6) + (i2 * 40),
|
|
|
|
int(j * 1.6) + (j2 * 30)
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
tile = absfree(
|
|
|
|
3 + (i * 3) + (i2 * 40),
|
|
|
|
(j * 3) + (j2 * 30)
|
|
|
|
);
|
|
|
|
break;
|
2021-02-20 05:51:25 +01:00
|
|
|
}
|
|
|
|
|
2022-11-30 22:25:28 +01:00
|
|
|
if (tile >= 1)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
2022-11-30 22:25:28 +01:00
|
|
|
// Fill in this pixel
|
|
|
|
FillRect(
|
|
|
|
graphics.images[12],
|
|
|
|
(i2 * 12 * map.customzoom) + i,
|
|
|
|
(j2 * 9 * map.customzoom) + j,
|
|
|
|
1,
|
|
|
|
1,
|
|
|
|
graphics.getRGB(tm, tm, tm)
|
|
|
|
);
|
2021-02-20 05:51:25 +01: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
|
2021-02-21 00:40:11 +01:00
|
|
|
Uint32 customlevelclass::getonewaycol(const int rx, const int ry)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
2021-02-21 00:45:48 +01:00
|
|
|
const RoomProperty* const room = getroomprop(rx, ry);
|
2021-02-20 05:51:25 +01:00
|
|
|
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
|
2021-02-21 00:40:11 +01:00
|
|
|
Uint32 customlevelclass::getonewaycol(void)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
2021-02-21 00:40:11 +01:00
|
|
|
#ifndef NO_EDITOR
|
2021-02-20 05:51:25 +01:00
|
|
|
if (game.gamestate == EDITORMODE)
|
2021-02-21 00:40:11 +01:00
|
|
|
{
|
|
|
|
return getonewaycol(ed.levx, ed.levy);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
#endif
|
|
|
|
if (map.custommode)
|
|
|
|
{
|
2021-02-20 05:51:25 +01:00
|
|
|
return getonewaycol(game.roomx - 100, game.roomy - 100);
|
2021-02-21 00:40:11 +01:00
|
|
|
}
|
2021-02-20 05:51:25 +01:00
|
|
|
|
|
|
|
// Uh, I guess return solid white
|
|
|
|
return graphics.getRGB(255, 255, 255);
|
|
|
|
}
|
|
|
|
|
2021-10-28 01:49:57 +02:00
|
|
|
static SDL_INLINE bool inbounds(const CustomEntity* entity)
|
|
|
|
{
|
|
|
|
extern customlevelclass cl;
|
|
|
|
return entity->x >= 0
|
|
|
|
&& entity->y >= 0
|
|
|
|
&& entity->x < cl.mapwidth * SCREEN_WIDTH_TILES
|
|
|
|
&& entity->y < cl.mapheight * SCREEN_HEIGHT_TILES;
|
|
|
|
}
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
int customlevelclass::numtrinkets(void)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
int temp = 0;
|
2021-02-21 01:01:39 +01:00
|
|
|
for (size_t i = 0; i < customentities.size(); i++)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
2021-10-28 01:49:57 +02:00
|
|
|
if (customentities[i].t == 9 && inbounds(&customentities[i]))
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
temp++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return temp;
|
|
|
|
}
|
|
|
|
|
2021-02-21 00:40:11 +01:00
|
|
|
int customlevelclass::numcrewmates(void)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
int temp = 0;
|
2021-02-21 01:01:39 +01:00
|
|
|
for (size_t i = 0; i < customentities.size(); i++)
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
2021-10-28 01:49:57 +02:00
|
|
|
if (customentities[i].t == 15 && inbounds(&customentities[i]))
|
2021-02-20 05:51:25 +01:00
|
|
|
{
|
|
|
|
temp++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return temp;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* NO_CUSTOM_LEVELS */
|