1
0
Fork 0
mirror of https://github.com/TerryCavanagh/VVVVVV.git synced 2024-12-23 01:59:43 +01:00
VVVVVV/desktop_version/src/CustomLevels.cpp
AllyTally a537492d9c Remove NO_EDITOR/NO_CUSTOM_LEVELS, disable editor on Steam Deck
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`.
2023-08-25 09:50:27 -07:00

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, "&quot;", "\"");
replace_all(value, "&amp;", "&");
replace_all(value, "&apos;", "'");
replace_all(value, "&lt;", "<");
replace_all(value, "&gt;", ">");
//Encode general XML entities
size_t start_pos = 0;
while ((start_pos = value.find("&#", start_pos)) != std::string::npos)
{
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 &blank;
}
#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 ...>
// &#32; &#32;this</edentity>, which TinyXML-1 never did.
//
// Best solution here is to specifically hardcode removing
// the linefeed + the extremely specific amount of
// whitespace at the end of the contents.
if (endsWith(text, "\n ")) // linefeed + exactly 12 spaces
{
// 12 spaces + 1 linefeed = 13 chars
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;
}