mirror of
https://github.com/TerryCavanagh/VVVVVV.git
synced 2024-11-13 06:29:41 +01:00
a537492d9c
This commit removes the `NO_EDITOR` and `NO_CUSTOM_LEVELS` defines, which cleans up the code a lot, and they weren't really needed anyways. This commit also disables the editor on the Steam Deck, and adds a program argument to re-enable the editor, `-enable-editor`.
1963 lines
51 KiB
C++
1963 lines
51 KiB
C++
#define CL_DEFINITION
|
|
#include "CustomLevels.h"
|
|
|
|
#include <physfs.h>
|
|
#include <stdio.h>
|
|
#include <string>
|
|
#include <tinyxml2.h>
|
|
|
|
#include "Alloc.h"
|
|
#include "Constants.h"
|
|
#include "Editor.h"
|
|
#include "Enums.h"
|
|
#include "FileSystemUtils.h"
|
|
#include "Font.h"
|
|
#include "Game.h"
|
|
#include "Graphics.h"
|
|
#include "GraphicsUtil.h"
|
|
#include "KeyPoll.h"
|
|
#include "Localization.h"
|
|
#include "LocalizationStorage.h"
|
|
#include "Map.h"
|
|
#include "Screen.h"
|
|
#include "Script.h"
|
|
#include "UTF8.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
|
|
|
|
#define VMULT(y) (y * SCREEN_WIDTH_TILES * maxwidth)
|
|
#define Y_INBOUNDS(y) (y >= 0 && y < SCREEN_HEIGHT_TILES * maxheight)
|
|
|
|
RoomProperty::RoomProperty(void)
|
|
{
|
|
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;
|
|
}
|
|
|
|
customlevelclass::customlevelclass(void)
|
|
{
|
|
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;
|
|
}
|
|
|
|
/* translate_title and translate_creator are used to display default title/author
|
|
* as being translated, while they're actually stored in English in the level file.
|
|
* This way we translate "Untitled Level" and "Unknown" without
|
|
* spreading around translations in level files posted online! */
|
|
std::string translate_title(const std::string& title, bool* is_gettext)
|
|
{
|
|
if (title == "Untitled Level")
|
|
{
|
|
*is_gettext = true;
|
|
return loc::gettext("Untitled Level");
|
|
}
|
|
*is_gettext = false;
|
|
return title;
|
|
}
|
|
|
|
std::string translate_creator(const std::string& creator, bool* is_gettext)
|
|
{
|
|
if (creator == "Unknown")
|
|
{
|
|
*is_gettext = true;
|
|
return loc::gettext("Unknown");
|
|
}
|
|
*is_gettext = false;
|
|
return creator;
|
|
}
|
|
|
|
static void levelZipCallback(const char* filename)
|
|
{
|
|
if (!FILESYSTEM_isFile(filename))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (endsWith(filename, ".zip"))
|
|
{
|
|
FILESYSTEM_loadZip(filename);
|
|
}
|
|
}
|
|
|
|
void customlevelclass::loadZips(void)
|
|
{
|
|
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);
|
|
}
|
|
value.replace(start_pos, end - start_pos + 1, UTF8_encode(character).bytes);
|
|
}
|
|
|
|
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")
|
|
TAG_FINDER(find_font, "font")
|
|
|
|
/* 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")
|
|
|
|
#undef TAG_FINDER
|
|
|
|
static void levelMetaDataCallback(const char* filename)
|
|
{
|
|
extern customlevelclass cl;
|
|
LevelMetaData temp;
|
|
std::string filename_ = filename;
|
|
|
|
if (!endsWith(filename, ".vvvvvv")
|
|
|| !FILESYSTEM_isFile(filename)
|
|
|| FILESYSTEM_isMounted(filename))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (cl.getLevelMetaData(filename_, temp))
|
|
{
|
|
cl.ListOfMetaData.push_back(temp);
|
|
}
|
|
else
|
|
{
|
|
vlog_warn("Level %s not found :(", filename_.c_str());
|
|
}
|
|
}
|
|
|
|
void customlevelclass::getDirectoryData(void)
|
|
{
|
|
|
|
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]);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
bool customlevelclass::getLevelMetaDataAndPlaytestArgs(const std::string& _path, LevelMetaData& _data, CliPlaytestArgs* pt_args)
|
|
{
|
|
unsigned char *uMem;
|
|
FILESYSTEM_loadFileToMemory(_path.c_str(), &uMem, NULL);
|
|
|
|
if (uMem == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
std::string buf((char*) uMem);
|
|
|
|
VVV_free(uMem);
|
|
|
|
if (find_metadata(buf) == "")
|
|
{
|
|
vlog_warn("Couldn't load metadata for %s", _path.c_str());
|
|
return false;
|
|
}
|
|
|
|
_data.creator = translate_creator(find_creator(buf), &_data.creator_is_gettext);
|
|
_data.title = translate_title(find_title(buf), &_data.title_is_gettext);
|
|
_data.Desc1 = find_desc1(buf);
|
|
_data.Desc2 = find_desc2(buf);
|
|
_data.Desc3 = find_desc3(buf);
|
|
_data.website = find_website(buf);
|
|
if (!font::find_main_font_by_name(find_font(buf).c_str(), &_data.level_main_font_idx))
|
|
{
|
|
_data.level_main_font_idx = font::get_font_idx_8x8();
|
|
}
|
|
|
|
|
|
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());
|
|
}
|
|
}
|
|
|
|
_data.filename = _path;
|
|
return true;
|
|
}
|
|
|
|
bool customlevelclass::getLevelMetaData(const std::string& _path, LevelMetaData& _data)
|
|
{
|
|
return getLevelMetaDataAndPlaytestArgs(_path, _data, NULL);
|
|
}
|
|
|
|
void customlevelclass::reset(void)
|
|
{
|
|
version=2; //New smaller format change is 2
|
|
|
|
mapwidth=5;
|
|
mapheight=5;
|
|
|
|
title="Untitled Level"; // Already translatable
|
|
creator="Unknown";
|
|
|
|
levmusic=0;
|
|
|
|
customentities.clear();
|
|
levmusic=0;
|
|
|
|
for (int j = 0; j < maxheight; j++)
|
|
{
|
|
for (int i = 0; i < maxwidth; i++)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
SDL_zeroa(contents);
|
|
|
|
script.clearcustom();
|
|
|
|
onewaycol_override = false;
|
|
|
|
script.textbox_colours.clear();
|
|
script.add_default_colours();
|
|
map.specialroomnames.clear();
|
|
}
|
|
|
|
const int* customlevelclass::loadlevel( int rxi, int ryi )
|
|
{
|
|
//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;
|
|
}
|
|
|
|
int customlevelclass::getlevelcol(const int tileset, const int tilecol)
|
|
{
|
|
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;
|
|
}
|
|
|
|
int customlevelclass::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 customlevelclass::getwarpbackground(int rx, int ry)
|
|
{
|
|
const RoomProperty* const room = getroomprop(rx, ry);
|
|
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;
|
|
}
|
|
}
|
|
|
|
int customlevelclass::gettileidx(
|
|
const int rx,
|
|
const int ry,
|
|
const int x,
|
|
const int y
|
|
) {
|
|
const int yoff = y + ry*30;
|
|
int mult;
|
|
int idx;
|
|
|
|
if (Y_INBOUNDS(yoff))
|
|
{
|
|
mult = VMULT(yoff);
|
|
}
|
|
else
|
|
{
|
|
mult = 0;
|
|
}
|
|
|
|
idx = x + rx*40 + mult;
|
|
|
|
return idx;
|
|
}
|
|
|
|
void customlevelclass::settile(
|
|
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;
|
|
}
|
|
|
|
int customlevelclass::gettile(
|
|
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];
|
|
}
|
|
|
|
int customlevelclass::getabstile(const int x, const int y)
|
|
{
|
|
int idx;
|
|
int yoff;
|
|
|
|
if (Y_INBOUNDS(y))
|
|
{
|
|
yoff = VMULT(y);
|
|
}
|
|
else
|
|
{
|
|
yoff = 0;
|
|
}
|
|
|
|
idx = x + yoff;
|
|
|
|
if (!INBOUNDS_ARR(idx, contents))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return contents[idx];
|
|
}
|
|
|
|
|
|
int customlevelclass::getroompropidx(const int rx, const int ry)
|
|
{
|
|
return rx + ry*maxwidth;
|
|
}
|
|
|
|
const RoomProperty* customlevelclass::getroomprop(const int rx, const int ry)
|
|
{
|
|
const int idx = getroompropidx(rx, ry);
|
|
|
|
if (INBOUNDS_ARR(idx, roomproperties))
|
|
{
|
|
return &roomproperties[idx];
|
|
}
|
|
|
|
static RoomProperty blank;
|
|
blank.tileset = 1;
|
|
blank.directmode = 1;
|
|
blank.roomname.clear();
|
|
|
|
return ␣
|
|
}
|
|
|
|
#define FOREACH_PROP(NAME, TYPE) \
|
|
void customlevelclass::setroom##NAME(const int rx, const int ry, const TYPE NAME) \
|
|
{ \
|
|
const int idx = getroompropidx(rx, ry); \
|
|
\
|
|
if (!INBOUNDS_ARR(idx, roomproperties)) \
|
|
{ \
|
|
return; \
|
|
} \
|
|
\
|
|
roomproperties[idx].NAME = NAME; \
|
|
}
|
|
|
|
ROOM_PROPERTIES
|
|
|
|
#undef FOREACH_PROP
|
|
|
|
|
|
int customlevelclass::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(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;
|
|
}
|
|
|
|
void customlevelclass::findstartpoint(void)
|
|
{
|
|
//Ok! Scan the room for the closest checkpoint
|
|
int testeditor=-1;
|
|
//First up; is there a start point on this screen?
|
|
for(size_t i=0; i<customentities.size(); i++)
|
|
{
|
|
//if() on screen
|
|
if(customentities[i].t==16 && testeditor==-1)
|
|
{
|
|
testeditor=i;
|
|
}
|
|
}
|
|
|
|
if(testeditor==-1)
|
|
{
|
|
game.edsavex = 160;
|
|
game.edsavey = 120;
|
|
game.edsaverx = 100;
|
|
game.edsavery = 100;
|
|
game.edsavegc = 0;
|
|
game.edsavey--;
|
|
game.edsavedir=1;
|
|
}
|
|
else
|
|
{
|
|
//Start point spawn
|
|
game.edsavex = (customentities[testeditor].x * 8) - 4;
|
|
game.edsavey = customentities[testeditor].y * 8;
|
|
game.edsaverx = 100 + customentities[testeditor].rx;
|
|
game.edsavery = 100 + customentities[testeditor].ry;
|
|
game.edsavegc = 0;
|
|
game.edsavey++;
|
|
game.edsavedir=1-customentities[testeditor].p1;
|
|
}
|
|
}
|
|
|
|
int customlevelclass::findtrinket(int t)
|
|
{
|
|
int ttrinket=0;
|
|
for(int i=0; i<(int)customentities.size(); i++)
|
|
{
|
|
if(i==t) return ttrinket;
|
|
if(customentities[i].t==9) ttrinket++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int customlevelclass::findcrewmate(int t)
|
|
{
|
|
int ttrinket=0;
|
|
for(int i=0; i<(int)customentities.size(); i++)
|
|
{
|
|
if(i==t) return ttrinket;
|
|
if(customentities[i].t==15) ttrinket++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int customlevelclass::findwarptoken(int t)
|
|
{
|
|
int ttrinket=0;
|
|
for(int i=0; i<(int)customentities.size(); i++)
|
|
{
|
|
if(i==t) return ttrinket;
|
|
if(customentities[i].t==13) ttrinket++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
bool customlevelclass::load(std::string _path)
|
|
{
|
|
tinyxml2::XMLDocument doc;
|
|
tinyxml2::XMLHandle hDoc(&doc);
|
|
tinyxml2::XMLElement* pElem;
|
|
|
|
reset();
|
|
ed.reset();
|
|
|
|
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))
|
|
{
|
|
FILESYSTEM_setLevelDirError(
|
|
loc::gettext("Level {path} not found"),
|
|
"path:str",
|
|
_path.c_str()
|
|
);
|
|
goto fail;
|
|
}
|
|
|
|
if (doc.Error())
|
|
{
|
|
FILESYSTEM_setLevelDirError(
|
|
loc::gettext("Error parsing {path}: {error}"),
|
|
"path:str, error:str",
|
|
_path.c_str(),
|
|
doc.ErrorStr()
|
|
);
|
|
goto fail;
|
|
}
|
|
|
|
ed.loaded_filepath = _path;
|
|
|
|
version = 0;
|
|
level_font_name = "font";
|
|
|
|
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)
|
|
{
|
|
creator = pText_;
|
|
}
|
|
|
|
if(SDL_strcmp(pKey_, "Title") == 0)
|
|
{
|
|
title = pText_;
|
|
}
|
|
|
|
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_, "font") == 0)
|
|
{
|
|
level_font_name = 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())
|
|
{
|
|
CustomEntity entity = CustomEntity();
|
|
const char* text = edEntityEl->GetText();
|
|
int global_x = 0;
|
|
int global_y = 0;
|
|
|
|
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", &global_x);
|
|
edEntityEl->QueryIntAttribute("y", &global_y);
|
|
entity.rx = global_x / SCREEN_WIDTH_TILES;
|
|
entity.x = global_x % SCREEN_WIDTH_TILES;
|
|
entity.ry = global_y / SCREEN_HEIGHT_TILES;
|
|
entity.y = global_y % SCREEN_HEIGHT_TILES;
|
|
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);
|
|
|
|
customentities.push_back(entity);
|
|
}
|
|
}
|
|
|
|
if (SDL_strcmp(pKey, "levelMetaData") == 0)
|
|
{
|
|
int i = 0;
|
|
for( tinyxml2::XMLElement* edLevelClassElement = pElem->FirstChildElement(); edLevelClassElement; edLevelClassElement=edLevelClassElement->NextSiblingElement())
|
|
{
|
|
if (!INBOUNDS_ARR(i, roomproperties))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(edLevelClassElement->GetText() != NULL)
|
|
{
|
|
roomproperties[i].roomname = std::string(edLevelClassElement->GetText()) ;
|
|
}
|
|
|
|
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);
|
|
|
|
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 (SDL_strcmp(pKey, "TextboxColours") == 0)
|
|
{
|
|
for (tinyxml2::XMLElement* textColourElement = pElem->FirstChildElement(); textColourElement; textColourElement = textColourElement->NextSiblingElement())
|
|
{
|
|
if (SDL_strcmp(textColourElement->Value(), "colour") == 0)
|
|
{
|
|
int r = 255;
|
|
int g = 255;
|
|
int b = 255;
|
|
|
|
textColourElement->QueryIntAttribute("r", &r);
|
|
textColourElement->QueryIntAttribute("g", &g);
|
|
textColourElement->QueryIntAttribute("b", &b);
|
|
|
|
const char* name = textColourElement->Attribute("name");
|
|
|
|
if (name != NULL)
|
|
{
|
|
SDL_Colour colour;
|
|
colour.r = r;
|
|
colour.g = g;
|
|
colour.b = b;
|
|
|
|
script.textbox_colours[name] = colour;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SDL_strcmp(pKey, "SpecialRoomnames") == 0)
|
|
{
|
|
for (tinyxml2::XMLElement* roomnameElement = pElem->FirstChildElement(); roomnameElement; roomnameElement = roomnameElement->NextSiblingElement())
|
|
{
|
|
const char* roomnameType = roomnameElement->Value();
|
|
Roomname name;
|
|
name.x = 0;
|
|
name.y = 0;
|
|
name.flag = -1;
|
|
name.loop = false;
|
|
name.type = RoomnameType_STATIC;
|
|
name.progress = 0;
|
|
name.delay = 0;
|
|
if (SDL_strcmp(roomnameType, "transform") == 0)
|
|
{
|
|
name.type = RoomnameType_TRANSFORM;
|
|
name.delay = 2;
|
|
}
|
|
else if (SDL_strcmp(roomnameType, "glitch") == 0)
|
|
{
|
|
name.type = RoomnameType_GLITCH;
|
|
name.progress = 1;
|
|
name.delay = -1;
|
|
}
|
|
|
|
name.text.clear();
|
|
|
|
roomnameElement->QueryIntAttribute("x", &name.x);
|
|
roomnameElement->QueryIntAttribute("y", &name.y);
|
|
roomnameElement->QueryIntAttribute("flag", &name.flag);
|
|
|
|
roomnameElement->QueryBoolAttribute("loop", &name.loop);
|
|
|
|
// Rooms start at (100, 100) instead of (0, 0), so offset the coordinates
|
|
name.x += 100;
|
|
name.y += 100;
|
|
|
|
if (name.type == RoomnameType_STATIC)
|
|
{
|
|
const char* text = roomnameElement->GetText();
|
|
if (text != NULL)
|
|
{
|
|
name.text.push_back(std::string(text));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Does it have children?
|
|
if (roomnameElement->FirstChildElement() == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
for (tinyxml2::XMLElement* textElement = roomnameElement->FirstChildElement(); textElement; textElement = textElement->NextSiblingElement())
|
|
{
|
|
if (SDL_strcmp(textElement->Value(), "text") == 0)
|
|
{
|
|
const char* text = textElement->GetText();
|
|
if (text != NULL)
|
|
{
|
|
name.text.push_back(std::string(text));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
map.specialroomnames.push_back(name);
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
temp_platv[i] = roomproperties[i].platv;
|
|
}
|
|
|
|
for (i = 0; i < numrooms; ++i)
|
|
{
|
|
if (x < mapwidth)
|
|
{
|
|
const int platv_idx = x + y * mapwidth;
|
|
if (INBOUNDS_ARR(platv_idx, temp_platv))
|
|
{
|
|
roomproperties[i].platv = temp_platv[platv_idx];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
roomproperties[i].platv = 4; /* default */
|
|
}
|
|
|
|
++x;
|
|
|
|
if (x >= maxwidth)
|
|
{
|
|
x = 0;
|
|
++y;
|
|
}
|
|
}
|
|
}
|
|
|
|
loc::loadtext_custom(_path.c_str());
|
|
font::load_custom(level_font_name.c_str());
|
|
|
|
version=2;
|
|
|
|
return true;
|
|
|
|
fail:
|
|
return false;
|
|
}
|
|
|
|
bool customlevelclass::save(const std::string& _path)
|
|
{
|
|
tinyxml2::XMLDocument doc;
|
|
|
|
std::string newpath("levels/" + _path);
|
|
|
|
// Try to preserve the XML of the currently-loaded one
|
|
bool already_exists = !ed.loaded_filepath.empty()
|
|
&& FILESYSTEM_loadTiXml2Document(ed.loaded_filepath.c_str(), doc);
|
|
if (!already_exists && !ed.loaded_filepath.empty())
|
|
{
|
|
vlog_error("Currently-loaded %s not found", ed.loaded_filepath.c_str());
|
|
}
|
|
|
|
ed.loaded_filepath = newpath;
|
|
|
|
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
|
|
xml::update_tag(msg, "Creator", creator.c_str());
|
|
|
|
xml::update_tag(msg, "Title", title.c_str());
|
|
|
|
xml::update_tag(msg, "Created", version);
|
|
|
|
xml::update_tag(msg, "Modified", modifier.c_str());
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
if (level_font_name != "" && level_font_name != "font")
|
|
{
|
|
xml::update_tag(msg, "font", level_font_name.c_str());
|
|
}
|
|
else
|
|
{
|
|
// Get rid of it completely, same as <onewaycol_override>
|
|
tinyxml2::XMLElement* element;
|
|
while ((element = msg->FirstChildElement("font")) != 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");
|
|
for(size_t i = 0; i < customentities.size(); i++)
|
|
{
|
|
tinyxml2::XMLElement *edentityElement = doc.NewElement( "edentity" );
|
|
const int global_x = customentities[i].rx * SCREEN_WIDTH_TILES + customentities[i].x;
|
|
const int global_y = customentities[i].ry * SCREEN_HEIGHT_TILES + customentities[i].y;
|
|
edentityElement->SetAttribute("x", global_x);
|
|
edentityElement->SetAttribute("y", global_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() )) ;
|
|
msg->LinkEndChild( edentityElement );
|
|
}
|
|
|
|
msg = xml::update_element_delete_contents(data, "levelMetaData");
|
|
|
|
int temp_platv[numrooms];
|
|
for (size_t i = 0; i < SDL_arraysize(temp_platv); ++i)
|
|
{
|
|
temp_platv[i] = 4; /* default */
|
|
}
|
|
|
|
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))
|
|
{
|
|
temp_platv[platv_idx] = roomproperties[i].platv;
|
|
}
|
|
}
|
|
|
|
++x;
|
|
|
|
if (x >= mapwidth)
|
|
{
|
|
/* Skip to next actual row. */
|
|
i += maxwidth - mapwidth;
|
|
x = 0;
|
|
++y;
|
|
}
|
|
}
|
|
}
|
|
|
|
for(size_t i = 0; i < SDL_arraysize(roomproperties); i++)
|
|
{
|
|
tinyxml2::XMLElement *roompropertyElement = doc.NewElement( "edLevelClass" );
|
|
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);
|
|
roompropertyElement->SetAttribute( "platv", temp_platv[i]);
|
|
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() )) ;
|
|
msg->LinkEndChild( roompropertyElement );
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void customlevelclass::generatecustomminimap(void)
|
|
{
|
|
map.customzoom = 1;
|
|
if (mapwidth <= 10 && mapheight <= 10)
|
|
{
|
|
map.customzoom = 2;
|
|
}
|
|
if (mapwidth <= 5 && mapheight <= 5)
|
|
{
|
|
map.customzoom = 4;
|
|
}
|
|
|
|
// Set minimap offsets
|
|
switch (map.customzoom)
|
|
{
|
|
case 4:
|
|
map.custommmxoff = 24 * (5 - mapwidth);
|
|
map.custommmyoff = 18 * (5 - mapheight);
|
|
break;
|
|
case 2:
|
|
map.custommmxoff = 12 * (10 - mapwidth);
|
|
map.custommmyoff = 9 * (10 - mapheight);
|
|
break;
|
|
default:
|
|
map.custommmxoff = 6 * (20 - mapwidth);
|
|
map.custommmyoff = int(4.5 * (20 - mapheight));
|
|
break;
|
|
}
|
|
|
|
map.custommmxsize = 240 - (map.custommmxoff * 2);
|
|
map.custommmysize = 180 - (map.custommmyoff * 2);
|
|
|
|
// Start drawing the minimap
|
|
|
|
SDL_Texture* target = SDL_GetRenderTarget(gameScreen.m_renderer);
|
|
graphics.set_render_target(graphics.images[IMAGE_CUSTOMMINIMAP]);
|
|
graphics.clear();
|
|
|
|
// Scan over the map size
|
|
for (int j2 = 0; j2 < mapheight; j2++)
|
|
{
|
|
for (int i2 = 0; i2 < mapwidth; i2++)
|
|
{
|
|
std::vector<SDL_Point> dark_points;
|
|
std::vector<SDL_Point> light_points;
|
|
|
|
bool dark = getroomprop(i2, j2)->tileset == 1;
|
|
|
|
// Ok, now scan over each square
|
|
for (int j = 0; j < 9 * map.customzoom; j++)
|
|
{
|
|
for (int i = 0; i < 12 * map.customzoom; i++)
|
|
{
|
|
int tile;
|
|
switch (map.customzoom)
|
|
{
|
|
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;
|
|
}
|
|
|
|
if (tile >= 1)
|
|
{
|
|
// Add this pixel
|
|
SDL_Point point = { (i2 * 12 * map.customzoom) + i, (j2 * 9 * map.customzoom) + j };
|
|
if (dark)
|
|
{
|
|
dark_points.push_back(point);
|
|
}
|
|
else
|
|
{
|
|
light_points.push_back(point);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Draw them all at once
|
|
if (!dark_points.empty())
|
|
{
|
|
graphics.draw_points(dark_points.data(), dark_points.size(), 96, 96, 96);
|
|
}
|
|
if (!light_points.empty())
|
|
{
|
|
graphics.draw_points(light_points.data(), light_points.size(), 196, 196, 196);
|
|
}
|
|
}
|
|
}
|
|
|
|
graphics.set_render_target(target);
|
|
}
|
|
|
|
// 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
|
|
SDL_Color customlevelclass::getonewaycol(const int rx, const int ry)
|
|
{
|
|
const RoomProperty* const room = getroomprop(rx, ry);
|
|
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
|
|
SDL_Color customlevelclass::getonewaycol(void)
|
|
{
|
|
if (game.gamestate == EDITORMODE)
|
|
{
|
|
return getonewaycol(ed.levx, ed.levy);
|
|
}
|
|
else if (map.custommode)
|
|
{
|
|
return getonewaycol(game.roomx - 100, game.roomy - 100);
|
|
}
|
|
|
|
// Uh, I guess return solid white
|
|
return graphics.getRGB(255, 255, 255);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
int customlevelclass::numtrinkets(void)
|
|
{
|
|
int temp = 0;
|
|
for (size_t i = 0; i < customentities.size(); i++)
|
|
{
|
|
if (customentities[i].t == 9 && inbounds(&customentities[i]))
|
|
{
|
|
temp++;
|
|
}
|
|
}
|
|
return temp;
|
|
}
|
|
|
|
int customlevelclass::numcrewmates(void)
|
|
{
|
|
int temp = 0;
|
|
for (size_t i = 0; i < customentities.size(); i++)
|
|
{
|
|
if (customentities[i].t == 15 && inbounds(&customentities[i]))
|
|
{
|
|
temp++;
|
|
}
|
|
}
|
|
return temp;
|
|
}
|