1
0
mirror of https://github.com/TerryCavanagh/VVVVVV.git synced 2024-06-02 19:13:31 +02:00
VVVVVV/desktop_version/src/Editor.cpp

4252 lines
125 KiB
C++
Raw Normal View History

#if !defined(NO_CUSTOM_LEVELS) && !defined(NO_EDITOR)
#define ED_DEFINITION
#include "Editor.h"
2020-01-01 21:29:24 +01:00
#include <string>
#include "Constants.h"
#include "CustomLevels.h"
#include "DeferCallbacks.h"
2020-01-01 21:29:24 +01:00
#include "Entity.h"
#include "Enums.h"
#include "Font.h"
#include "Game.h"
#include "Graphics.h"
#include "GraphicsUtil.h"
2020-01-01 21:29:24 +01:00
#include "KeyPoll.h"
#include "Localization.h"
2020-01-01 21:29:24 +01:00
#include "Map.h"
#include "Maths.h"
#include "Music.h"
#include "Screen.h"
2020-01-01 21:29:24 +01:00
#include "Script.h"
2023-02-23 04:13:15 +01:00
#include "UTF8.h"
#include "UtilityClass.h"
#include "VFormat.h"
#include "Vlogging.h"
2020-01-01 21:29:24 +01:00
editorclass::editorclass(void)
{
reset();
register_tool(EditorTool_WALLS, "Walls", "1", SDLK_1, false);
register_tool(EditorTool_BACKING, "Backing", "2", SDLK_2, false);
register_tool(EditorTool_SPIKES, "Spikes", "3", SDLK_3, false);
register_tool(EditorTool_TRINKETS, "Trinkets", "4", SDLK_4, false);
register_tool(EditorTool_CHECKPOINTS, "Checkpoints", "5", SDLK_5, false);
register_tool(EditorTool_DISAPPEARING_PLATFORMS, "Disappear", "6", SDLK_6, false);
register_tool(EditorTool_CONVEYORS, "Conveyors", "7", SDLK_7, false);
register_tool(EditorTool_MOVING_PLATFORMS, "Moving", "8", SDLK_8, false);
register_tool(EditorTool_ENEMIES, "Enemies", "9", SDLK_9, false);
register_tool(EditorTool_GRAVITY_LINES, "Grav Lines", "0", SDLK_0, false);
register_tool(EditorTool_ROOMTEXT, "Roomtext", "R", SDLK_r, false);
register_tool(EditorTool_TERMINALS, "Terminals", "T", SDLK_t, false);
register_tool(EditorTool_SCRIPTS, "Script Boxes", "Y", SDLK_y, false);
register_tool(EditorTool_WARP_TOKENS, "Warp Tokens", "U", SDLK_u, false);
register_tool(EditorTool_WARP_LINES, "Warp Lines", "I", SDLK_i, false);
register_tool(EditorTool_CREWMATES, "Crewmates", "O", SDLK_o, false);
register_tool(EditorTool_START_POINT, "Start Point", "P", SDLK_p, false);
}
void editorclass::reset(void)
{
current_tool = EditorTool_WALLS;
roomnamehide = 0;
zmod = false;
xmod = false;
cmod = false;
vmod = false;
hmod = false;
bmod = false;
spacemod = false;
shiftmenu = false;
shiftkey = false;
saveandquit = false;
note = "";
notedelay = 0;
oldnotedelay = 0;
deletekeyheld = false;
textmod = TEXT_NONE;
titlemod = false;
creatormod = false;
desc1mod = false;
desc2mod = false;
desc3mod = false;
websitemod = false;
settingsmod = false;
warpmod = false; //Two step process
warpent = -1;
boundarymod = 0;
boundarytype = 0;
boundx1 = 0;
boundx2 = 0;
boundy1 = 0;
boundy2 = 0;
textent = 0;
scripttexttype = 0;
drawmode = 0;
dmtile = 0;
dmtileeditor = 0;
entcol = 0;
tilex = 0;
tiley = 0;
levx = 0;
levy = 0;
keydelay = 0;
lclickdelay = 0;
savekey = false;
loadkey = false;
updatetiles = true;
changeroom = true;
entframe = 0;
entframedelay = 0;
SDL_zeroa(kludgewarpdir);
hooklist.clear();
sb.clear();
clearscriptbuffer();
sbx = 0;
sby = 0;
pagey = 0;
lines_visible = 25;
scripteditmod = false;
sbscript = "null";
scripthelppage = 0;
scripthelppagedelay = 0;
hookmenupage = 0;
hookmenu = 0;
returneditoralpha = 0;
oldreturneditoralpha = 0;
ghosts.clear();
currentghosts = 0;
loaded_filepath = "";
state = EditorState_DRAW;
substate = EditorSubState_MAIN;
}
void editorclass::handle_tile_placement(const int tile)
{
int range = 1;
if (bmod)
{
// Vertical line
for (int i = 0; i < 30; i++)
{
settile(tilex, i, tile);
}
return;
}
else if (hmod)
{
// Horizontal line
for (int i = 0; i < 40; i++)
{
settile(i, tiley, tile);
}
return;
}
else if (vmod)
{
range = 4;
}
else if (cmod)
{
range = 3;
}
else if (xmod)
{
range = 2;
}
else if (zmod)
{
range = 1;
}
else
{
settile(tilex, tiley, tile);
return;
}
for (int i = -range; i <= range; i++)
{
for (int j = -range; j <= range; j++)
{
settile(tilex + i, tiley + j, tile);
}
}
}
void editorclass::tool_remove()
{
switch (current_tool)
{
case EditorTool_WALLS:
case EditorTool_BACKING:
handle_tile_placement(0);
break;
default:
break;
}
for (size_t i = 0; i < customentities.size(); i++)
{
if (customentities[i].x == tilex + (levx * 40) && customentities[i].y == tiley + (levy * 30))
{
removeedentity(i);
}
}
}
void editorclass::entity_clicked(const int index)
{
CustomEntity* entity = &customentities[index];
lclickdelay = 1;
switch (entity->t)
{
case 1:
// Enemies
entity->p1 = (entity->p1 + 1) % 4;
break;
case 2:
{
// Moving Platforms and Conveyors
const bool conveyor = entity->p1 >= 5;
entity->p1++;
if (conveyor)
{
entity->p1 = (entity->p1 - 5) % 4 + 5;
}
else
{
entity->p1 %= 4;
}
break;
}
case 10:
// Checkpoints
// If it's not textured as a checkpoint, then just leave it be
if (entity->p1 == 0 || entity->p1 == 1)
{
entity->p1 = (entity->p1 + 1) % 2;
}
break;
case 11:
case 16:
// Gravity Lines, Start Point
entity->p1 = (entity->p1 + 1) % 2;
break;
case 15:
// Crewmates
entity->p1 = (entity->p1 + 1) % 6;
break;
case 17:
// Roomtext
getlin(TEXT_ROOMTEXT, loc::gettext("Enter roomtext:"), &entity->scriptname);
textent = index;
break;
case 18:
// Terminals
if (entity->p1 == 0 || entity->p1 == 1)
{
// Flip the terminal, but if it's not textured as a terminal leave it alone
entity->p1 = (entity->p1 + 1) % 2;
}
SDL_FALLTHROUGH;
case 19:
// Script Boxes (and terminals)
getlin(TEXT_SCRIPT, loc::gettext("Enter script name:"), &entity->scriptname);
textent = index;
break;
}
}
void editorclass::tool_place()
{
const int entity = edentat(tilex + (levx * 40), tiley + (levy * 30));
if (entity != -1)
{
entity_clicked(entity);
return;
}
switch (current_tool)
{
case EditorTool_WALLS:
case EditorTool_BACKING:
{
int tile = 0;
if (cl.getroomprop(levx, levy)->directmode >= 1)
{
tile = dmtile;
}
else if (current_tool == EditorTool_WALLS)
{
tile = 1;
}
else if (current_tool == EditorTool_BACKING)
{
tile = 2;
}
handle_tile_placement(tile);
break;
}
case EditorTool_SPIKES:
settile(tilex, tiley, 8);
break;
case EditorTool_TRINKETS:
if (cl.numtrinkets() < 100)
{
addedentity(tilex + (levx * 40), tiley + (levy * 30), 9);
lclickdelay = 1;
}
else
{
note = loc::gettext("ERROR: Max number of trinkets is 100");
notedelay = 45;
}
break;
case EditorTool_CHECKPOINTS:
addedentity(tilex + (levx * 40), tiley + (levy * 30), 10, 1);
lclickdelay = 1;
break;
case EditorTool_DISAPPEARING_PLATFORMS:
addedentity(tilex + (levx * 40), tiley + (levy * 30), 3);
lclickdelay = 1;
break;
case EditorTool_CONVEYORS:
addedentity(tilex + (levx * 40), tiley + (levy * 30), 2, 5);
lclickdelay = 1;
break;
case EditorTool_MOVING_PLATFORMS:
addedentity(tilex + (levx * 40), tiley + (levy * 30), 2, 0);
lclickdelay = 1;
break;
case EditorTool_ENEMIES:
addedentity(tilex + (levx * 40), tiley + (levy * 30), 1, 0);
lclickdelay = 1;
break;
case EditorTool_GRAVITY_LINES:
addedentity(tilex + (levx * 40), tiley + (levy * 30), 11, 0);
lclickdelay = 1;
break;
case EditorTool_ROOMTEXT:
lclickdelay = 1;
textent = customentities.size();
addedentity(tilex + (levx * 40), tiley + (levy * 30), 17);
getlin(TEXT_ROOMTEXT, loc::gettext("Enter roomtext:"), &(customentities[textent].scriptname));
break;
case EditorTool_TERMINALS:
lclickdelay = 1;
textent = customentities.size();
addedentity(tilex + (levx * 40), tiley + (levy * 30), 18, 0);
getlin(TEXT_SCRIPT, loc::gettext("Enter script name:"), &(customentities[textent].scriptname));
break;
case EditorTool_SCRIPTS:
boundarytype = 0;
boundx1 = tilex * 8;
boundy1 = tiley * 8;
boundarymod = 2;
lclickdelay = 1;
break;
case EditorTool_WARP_TOKENS:
warpmod = true;
warpent = customentities.size();
addedentity(tilex + (levx * 40), tiley + (levy * 30), 13);
lclickdelay = 1;
break;
case EditorTool_WARP_LINES:
//Warp lines
if (tilex == 0)
{
addedentity(tilex + (levx * 40), tiley + (levy * 30), 50, 0);
}
else if (tilex == 39)
{
addedentity(tilex + (levx * 40), tiley + (levy * 30), 50, 1);
}
else if (tiley == 0)
{
addedentity(tilex + (levx * 40), tiley + (levy * 30), 50, 2);
}
else if (tiley == 29)
{
addedentity(tilex + (levx * 40), tiley + (levy * 30), 50, 3);
}
else
{
note = loc::gettext("ERROR: Warp lines must be on edges");
notedelay = 45;
}
lclickdelay = 1;
break;
case EditorTool_CREWMATES:
if (cl.numcrewmates() < 100)
{
addedentity(tilex + (levx * 40), tiley + (levy * 30), 15, int(fRandom() * 6));
lclickdelay = 1;
}
else
{
note = loc::gettext("ERROR: Max number of crewmates is 100");
notedelay = 45;
}
break;
case EditorTool_START_POINT:
//If there is another start point, destroy it
for (size_t i = 0; i < customentities.size(); i++)
{
if (customentities[i].t == 16)
{
removeedentity(i);
i--;
}
}
addedentity(tilex + (levx * 40), tiley + (levy * 30), 16, 0);
lclickdelay = 1;
break;
}
}
void editorclass::draw_tool(EditorTools tool, int x, int y)
{
extern editorclass ed;
switch (tool)
{
case EditorTool_WALLS:
graphics.drawtile(x, y, 83);
graphics.drawtile(x + 8, y, 83);
graphics.drawtile(x, y + 8, 83);
graphics.drawtile(x + 8, y + 8, 83);
break;
case EditorTool_BACKING:
graphics.drawtile(x, y, 680);
graphics.drawtile(x + 8, y, 680);
graphics.drawtile(x, y + 8, 680);
graphics.drawtile(x + 8, y + 8, 680);
break;
case EditorTool_SPIKES:
graphics.drawtile(x + 4, y + 4, 8);
break;
case EditorTool_TRINKETS:
graphics.draw_sprite(x, y, 22, 196, 196, 196);
break;
case EditorTool_CHECKPOINTS:
graphics.draw_sprite(x, y, 21, 196, 196, 196);
break;
case EditorTool_DISAPPEARING_PLATFORMS:
graphics.drawtile(x, y + 4, 3);
graphics.drawtile(x + 8, y + 4, 4);
break;
case EditorTool_CONVEYORS:
graphics.drawtile(x, y + 4, 24);
graphics.drawtile(x + 8, y + 4, 24);
break;
case EditorTool_MOVING_PLATFORMS:
graphics.drawtile(x, y + 4, 1);
graphics.drawtile(x + 8, y + 4, 1);
break;
case EditorTool_ENEMIES:
graphics.draw_sprite(x, y, 78 + ed.entframe, 196, 196, 196);
break;
case EditorTool_GRAVITY_LINES:
graphics.fill_rect(x + 2, y + 8, 12, 1, graphics.getRGB(255, 255, 255));
break;
case EditorTool_ROOMTEXT:
font::print(PR_FONT_8X8, x + 1, y, "AB", 196, 196, 255 - help.glow);
font::print(PR_FONT_8X8, x + 1, y + 9, "CD", 196, 196, 255 - help.glow);
break;
case EditorTool_TERMINALS:
graphics.draw_sprite(x, y, 17, 196, 196, 196);
break;
case EditorTool_SCRIPTS:
graphics.draw_rect(x + 4, y + 4, 8, 8, graphics.getRGB(96, 96, 96));
break;
case EditorTool_WARP_TOKENS:
graphics.draw_sprite(x, y, 18 + (ed.entframe % 2), 196, 196, 196);
break;
case EditorTool_WARP_LINES:
graphics.fill_rect(x + 6, y + 2, 4, 12, graphics.getRGB(255, 255, 255));
break;
case EditorTool_CREWMATES:
graphics.draw_sprite(x, y, 186, graphics.col_crewblue);
break;
case EditorTool_START_POINT:
graphics.draw_sprite(x, y, 184, graphics.col_crewcyan);
break;
}
}
void editorclass::register_tool(EditorTools tool, const char* name, const char* keychar, const SDL_KeyCode key, const bool shift)
{
toolnames[tool] = name;
toolkeychar[tool] = keychar;
toolkey[tool] = key;
toolshift[tool] = shift;
}
void editorclass::gethooks(void)
{
// Scan through the script and create a hooks list based on it
hooklist.clear();
for (size_t i = 0; i < script.customscripts.size(); i++)
{
Script& script_ = script.customscripts[i];
hooklist.push_back(script_.name);
}
}
void editorclass::loadhookineditor(const std::string& t)
{
//Find hook t in the scriptclass, then load it into the editor
clearscriptbuffer();
for(size_t i = 0; i < script.customscripts.size(); i++)
{
Script& script_ = script.customscripts[i];
if(script_.name == t)
{
sb = script_.contents;
break;
}
}
if(sb.empty())
{
//Always have one line or we'll have problems
sb.resize(1);
}
}
void editorclass::addhooktoscript(const std::string& t)
{
//Adds hook+the scriptbuffer to the end of the scriptclass
removehookfromscript(t);
Script script_;
script_.name = t;
script_.contents = sb;
script.customscripts.push_back(script_);
}
void editorclass::removehookfromscript(const std::string& t)
{
/* Find hook t in the scriptclass, then removes it (and any other code with it)
* When this loop reaches the end, it wraps to SIZE_MAX; SIZE_MAX + 1 is 0 */
size_t i;
for (i = script.customscripts.size() - 1; i + 1 > 0; --i)
{
if (script.customscripts[i].name == t)
{
script.customscripts.erase(script.customscripts.begin() + i);
}
}
}
void editorclass::removehook(const std::string& t)
{
//Check the hooklist for the hook t. If it's there, remove it from here and the script
size_t i;
removehookfromscript(t);
/* When this loop reaches the end, it wraps to SIZE_MAX; SIZE_MAX + 1 is 0 */
for (i = hooklist.size() - 1; i + 1 > 0; --i)
{
if (hooklist[i] == t)
{
hooklist.erase(hooklist.begin() + i);
}
}
}
void editorclass::addhook(const std::string& t)
{
//Add an empty function to the list in both editor and script
removehook(t);
hooklist.push_back(t);
addhooktoscript(t);
}
bool editorclass::checkhook(const std::string& t)
{
//returns true if hook t already is in the list
for(size_t i=0; i<hooklist.size(); i++)
{
if(hooklist[i]==t) return true;
}
return false;
}
void editorclass::clearscriptbuffer(void)
{
sb.clear();
}
void editorclass::removeline(int t)
{
//Remove line t from the script
if ((int) sb.size() > 1)
{
sb.erase(sb.begin() + t);
}
}
void editorclass::insertline(int t)
{
//insert a blank line into script at line t
sb.insert(sb.begin() + t, "");
}
void editorclass::getlin(const enum textmode mode, const std::string& prompt, std::string* ptr)
{
textmod = mode;
textptr = ptr;
textdesc = prompt;
key.enabletextentry();
if (ptr)
{
key.keybuffer = *ptr;
}
else
{
key.keybuffer = "";
textptr = &(key.keybuffer);
}
oldenttext = key.keybuffer;
}
void editorclass::addedentity(int xp, int yp, int tp, int p1, int p2, int p3, int p4, int p5, int p6)
2020-01-01 21:29:24 +01:00
{
CustomEntity entity;
entity.x = xp;
entity.y = yp;
entity.t = tp;
entity.p1 = p1;
entity.p2 = p2;
entity.p3 = p3;
entity.p4 = p4;
entity.p5 = p5;
entity.p6 = p6;
entity.scriptname = "";
customentities.push_back(entity);
2020-01-01 21:29:24 +01:00
}
void editorclass::removeedentity(int t)
2020-01-01 21:29:24 +01:00
{
customentities.erase(customentities.begin() + t);
2020-01-01 21:29:24 +01:00
}
int editorclass::edentat(int xp, int yp)
2020-01-01 21:29:24 +01:00
{
for (size_t i = 0; i < customentities.size(); i++)
2020-01-01 21:29:24 +01:00
{
if (customentities[i].x == xp && customentities[i].y == yp) return i;
2020-01-01 21:29:24 +01:00
}
return -1;
}
static void editormenurender(int tr, int tg, int tb)
{
extern editorclass ed;
switch (game.currentmenuname)
{
case Menu::ed_settings:
font::print(PR_2X | PR_CEN, -1, 75, loc::gettext("Map Settings"), tr, tg, tb);
if (game.currentmenuoption == 3)
{
if (!game.ghostsenabled)
font::print(0, 2, 230, loc::gettext("Editor ghost trail is OFF"), tr/2, tg/2, tb/2);
else
font::print(0, 2, 230, loc::gettext("Editor ghost trail is ON"), tr, tg, tb);
}
break;
case Menu::ed_desc:
{
if(ed.titlemod)
{
if(ed.entframe<2)
{
font::print(PR_2X | PR_CEN | PR_FONT_LEVEL, -1, 35, key.keybuffer+"_", tr, tg, tb);
}
else
{
font::print(PR_2X | PR_CEN | PR_FONT_LEVEL, -1, 35, key.keybuffer+" ", tr, tg, tb);
}
}
else
{
bool title_is_gettext;
std::string title = translate_title(cl.title, &title_is_gettext);
font::print(PR_2X | PR_CEN | (title_is_gettext ? PR_FONT_INTERFACE : PR_FONT_LEVEL), -1, 35, title, tr, tg, tb);
}
bool creator_is_gettext = false;
std::string creator;
if(ed.creatormod)
{
if(ed.entframe<2)
{
creator = key.keybuffer + "_";
}
else
{
creator = key.keybuffer + " ";
}
}
else
{
creator = translate_creator(cl.creator, &creator_is_gettext);
}
int sp = SDL_max(10, font::height(PR_FONT_LEVEL));
Replace "by" for level authors with happy face "by {author}" is a string that will cause a lot of localization-related problems, which then become much worse when different languages and levels can also need different fonts: - If the author name is set to something in English instead of a name, then it'll come out a bit weird if your VVVVVV is set to a different language: "de various people", "por various people", etc. It's the same problem with Discord bots completing "playing" or "watching" in their statuses. - Translators can't always fit "by" in two letters, and level creators have understandably always assumed, and will continue to assume, that "by" is two letters. So if you have your VVVVVV set to a language that translates "by" as something long, then: | by Various People and Others | ...may suddenly show up as something like: |thorer Various People and Othe| - "by" and author may need mutually incompatible fonts. For example, a Japanese level in a Korean VVVVVV needs to be displayed with "by" in Korean characters and the author name with Japanese characters, which would need some very special code since languages may want to add text both before and after the name. - It's very possible that some languages can't translate "by" without knowing the gender of the name, and I know some languages even inflect names in really interesting ways (adding and even replacing letters in first names, surnames, and anything in between, depending on gender and what else is in the sentence). So to solve all of this, the "by" is now replaced by a 10x10 face from sprites.png, like a :viridian: emote. See it as a kind of avatar next to a username, to clarify and assert that this line is for the author's name. It should be a fairly obvious/recognizable icon, it fixes all the above problems, and it's a bonus that we now have more happy faces in VVVVVV.
2023-01-31 00:41:46 +01:00
graphics.print_level_creator((creator_is_gettext ? PR_FONT_INTERFACE : PR_FONT_LEVEL), 60, creator, tr, tg, tb);
if(ed.websitemod)
{
if(ed.entframe<2)
{
font::print(PR_CEN | PR_FONT_LEVEL, -1, 60+sp, key.keybuffer+"_", tr, tg, tb);
}
else
{
font::print(PR_CEN | PR_FONT_LEVEL, -1, 60+sp, key.keybuffer+" ", tr, tg, tb);
}
}
else
{
font::print(PR_CEN | PR_FONT_LEVEL, -1, 60+sp, cl.website, tr, tg, tb);
}
if(ed.desc1mod)
{
if(ed.entframe<2)
{
font::print(PR_CEN | PR_FONT_LEVEL, -1, 60+sp*3, key.keybuffer+"_", tr, tg, tb);
}
else
{
font::print(PR_CEN | PR_FONT_LEVEL, -1, 60+sp*3, key.keybuffer+" ", tr, tg, tb);
}
}
else
{
font::print(PR_CEN | PR_FONT_LEVEL, -1, 60+sp*3, cl.Desc1, tr, tg, tb);
}
if(ed.desc2mod)
{
if(ed.entframe<2)
{
font::print(PR_CEN | PR_FONT_LEVEL, -1, 60+sp*4, key.keybuffer+"_", tr, tg, tb);
}
else
{
font::print(PR_CEN | PR_FONT_LEVEL, -1, 60+sp*4, key.keybuffer+" ", tr, tg, tb);
}
}
else
{
font::print(PR_CEN | PR_FONT_LEVEL, -1, 60+sp*4, cl.Desc2, tr, tg, tb);
}
if(ed.desc3mod)
{
if(ed.entframe<2)
{
font::print(PR_CEN | PR_FONT_LEVEL, -1, 60+sp*5, key.keybuffer+"_", tr, tg, tb);
}
else
{
font::print(PR_CEN | PR_FONT_LEVEL, -1, 60+sp*5, key.keybuffer+" ", tr, tg, tb);
}
}
else if (sp <= 10)
{
font::print(PR_CEN | PR_FONT_LEVEL, -1, 60+sp*5, cl.Desc3, tr, tg, tb);
}
const char* label = loc::gettext("Font: ");
int len_label = font::len(0, label);
const char* name = font::get_level_font_display_name();
font::print(0, 2, 230, label, tr/2, tg/2, tb/2);
font::print(PR_FONT_LEVEL, 2+len_label, 230, name, tr/2, tg/2, tb/2);
break;
}
case Menu::ed_music:
{
font::print(PR_2X | PR_CEN | PR_CJK_HIGH, -1, 65, loc::gettext("Map Music"), tr, tg, tb);
font::print_wrap(PR_CEN | PR_CJK_LOW, -1, 85, loc::gettext("Current map music:"), tr, tg, tb);
const char* songname;
switch(cl.levmusic)
{
case 0:
songname = loc::gettext("No background music");
break;
case 1:
songname = loc::gettext("1: Pushing Onwards");
break;
case 2:
songname = loc::gettext("2: Positive Force");
break;
case 3:
songname = loc::gettext("3: Potential for Anything");
break;
case 4:
songname = loc::gettext("4: Passion for Exploring");
break;
case 5:
songname = loc::gettext("N/A: Pause");
break;
case 6:
songname = loc::gettext("5: Presenting VVVVVV");
break;
case 7:
songname = loc::gettext("N/A: Plenary");
break;
case 8:
songname = loc::gettext("6: Predestined Fate");
break;
case 9:
songname = loc::gettext("N/A: ecroF evitisoP");
break;
case 10:
songname = loc::gettext("7: Popular Potpourri");
break;
case 11:
songname = loc::gettext("8: Pipe Dream");
break;
case 12:
songname = loc::gettext("9: Pressure Cooker");
break;
case 13:
songname = loc::gettext("10: Paced Energy");
break;
case 14:
songname = loc::gettext("11: Piercing the Sky");
break;
case 15:
songname = loc::gettext("N/A: Predestined Fate Remix");
break;
default:
songname = loc::gettext("?: something else");
break;
}
font::print_wrap(PR_CEN, -1, 120, songname, tr, tg, tb);
break;
}
case Menu::ed_quit:
font::print_wrap(PR_CEN, -1, 90, loc::gettext("Save before quitting?"), tr, tg, tb);
break;
case Menu::ed_font:
{
font::print(PR_2X | PR_CEN, -1, 30, loc::gettext("Level Font"), tr, tg, tb);
font::print_wrap(PR_CEN, -1, 65, loc::gettext("Select the language in which the text in this level is written."), tr, tg, tb);
const char* label = loc::gettext("Font: ");
int len_label = font::len(0, label);
const char* name = font::get_level_font_display_name();
font::print(0, 2, 230, label, tr/2, tg/2, tb/2);
font::print(PR_FONT_LEVEL, 2+len_label, 230, name, tr/2, tg/2, tb/2);
break;
}
default:
break;
}
}
static void draw_background_grid(void)
2020-01-01 21:29:24 +01:00
{
for (int j = 0; j < 30; j++)
2020-01-01 21:29:24 +01:00
{
for (int i = 0; i < 40; i++)
2020-01-01 21:29:24 +01:00
{
if (i == 19 || i == 20 || j == 14 || j == 29)
2020-01-01 21:29:24 +01:00
{
// Major guidelines
graphics.draw_rect(i * 8, j * 8, 7, 7, graphics.getRGB(32, 32, 32));
2020-01-01 21:29:24 +01:00
}
else if (i == 9 || i == 30 || j == 6 || j == 7 || j == 21 || j == 22)
{
// Minor guidelines
graphics.draw_rect(i * 8, j * 8, 7, 7, graphics.getRGB(24, 24, 24));
}
else if (i % 4 == 0 || j % 4 == 0)
{
graphics.draw_rect(i * 8, j * 8, 7, 7, graphics.getRGB(16, 16, 16));
}
else
2020-01-01 21:29:24 +01:00
{
graphics.draw_rect(i * 8, j * 8, 7, 7, graphics.getRGB(8, 8, 8));
2020-01-01 21:29:24 +01:00
}
}
}
}
2020-01-01 21:29:24 +01:00
static void draw_background(int warpdir)
{
extern editorclass ed;
2020-01-01 21:29:24 +01:00
switch (warpdir)
2020-01-01 21:29:24 +01:00
{
case 1:
graphics.rcol = cl.getwarpbackground(ed.levx, ed.levy);
graphics.drawbackground(3);
break;
case 2:
graphics.rcol = cl.getwarpbackground(ed.levx, ed.levy);
graphics.drawbackground(4);
break;
case 3:
graphics.rcol = cl.getwarpbackground(ed.levx, ed.levy);
graphics.drawbackground(5);
break;
default:
break;
}
}
static void draw_edgeguide(int type, int x, int y, bool vertical)
{
static const SDL_Color white = graphics.getRGB(255 - help.glow, 255, 255);
static const SDL_Color red = graphics.getRGB(255 - help.glow, 127, 127);
if (type != TileType_SOLID && type != TileType_SPIKE)
{
return;
2020-01-01 21:29:24 +01:00
}
if (vertical)
2020-01-01 21:29:24 +01:00
{
graphics.fill_rect(x, y, 8, 2, (type == TileType_SOLID) ? white : red);
}
else
{
graphics.fill_rect(x, y, 2, 8, (type == TileType_SOLID) ? white : red);
}
}
static void draw_edgeguides(void)
{
extern editorclass ed;
const int global_x = ed.levx * 40;
const int global_y = ed.levy * 30;
2020-01-01 21:29:24 +01:00
// Draw edge-guides, so there's no room misalignments!
for (int i = 0; i < 40; i++)
{
if (i < 30)
2020-01-01 21:29:24 +01:00
{
// Left edge
draw_edgeguide(ed.tile_type_wrap(global_x - 1, global_y + i), 0, i * 8, false);
// Right edge
draw_edgeguide(ed.tile_type_wrap(global_x + 40, global_y + i), 318, i * 8, false);
2020-01-01 21:29:24 +01:00
}
// Top edge
draw_edgeguide(ed.tile_type_wrap(global_x + i, global_y - 1), i * 8, 0, true);
// Bottom edge
draw_edgeguide(ed.tile_type_wrap(global_x + i, global_y + 30), i * 8, 238, true);
2020-01-01 21:29:24 +01:00
}
}
static void update_entities(void)
{
extern editorclass ed;
for (size_t i = 0; i < customentities.size(); ++i)
{
CustomEntity* entity = &customentities[i];
if (entity->x / 40 != ed.levx || entity->y / 30 != ed.levy)
{
// It's not in this room, so just continue
continue;
}
bool grav_line = (entity->t == 11);
bool warp_line = (entity->t == 50);
if ((grav_line || warp_line) && entity->p4 != 1)
{
// If it's a grav line or a warp line, and it's not locked
if ((grav_line && entity->p1 == 0) || (warp_line && entity->p1 >= 2))
{
/* Horizontal */
int tx = entity->x % 40;
int tx2 = tx;
int ty = entity->y % 30;
while (!ed.spikefree(tx, ty))
{
--tx;
}
while (!ed.spikefree(tx2, ty))
{
++tx2;
}
++tx;
entity->p2 = tx;
entity->p3 = (tx2 - tx) * 8;
}
else
{
/* Vertical */
int tx = entity->x % 40;
int ty = entity->y % 30;
int ty2 = ty;
while (!ed.spikefree(tx, ty))
{
--ty;
}
while (!ed.spikefree(tx, ty2))
{
++ty2;
}
++ty;
entity->p2 = ty;
entity->p3 = (ty2 - ty) * 8;
}
}
}
}
static void draw_entities(void)
{
extern editorclass ed;
const RoomProperty* const room = cl.getroomprop(ed.levx, ed.levy);
2020-01-01 21:29:24 +01:00
//Draw entities
obj.customplatformtile = game.customcol * 12;
2020-01-01 21:29:24 +01:00
const int edent_under_cursor = ed.edentat(ed.tilex + ed.levx * 40, ed.tiley + ed.levy * 30);
// Special case for drawing gray entities
bool custom_gray = room->tileset == 3 && room->tilecol == 6;
// Draw entities backward to remain accurate with ingame
for (int i = customentities.size() - 1; i >= 0; i--)
2020-01-01 21:29:24 +01:00
{
CustomEntity* entity = &customentities[i];
2020-01-01 21:29:24 +01:00
// If the entity is in the current room, draw it
if (entity->x / 40 == ed.levx && entity->y / 30 == ed.levy)
2020-01-01 21:29:24 +01:00
{
const int x = entity->x % 40 * 8;
const int y = entity->y % 30 * 8;
static const char arrows[] = "V^<>";
switch (entity->t)
2020-01-01 21:29:24 +01:00
{
case 1: // Enemies
{
const int movement = entity->p1;
if (custom_gray)
{
ed.entcolreal = graphics.getcol(18);
}
2020-01-01 21:29:24 +01:00
graphics.draw_sprite(x, y, ed.getenemyframe(room->enemytype), ed.entcolreal);
2020-01-01 21:29:24 +01:00
if (movement >= 0 && movement < 4)
2020-01-01 21:29:24 +01:00
{
// If they have a basic movement type, draw an arrow to indicate direction
font::print(PR_FONT_8X8, x + 4, y + 4, std::string(1, arrows[movement]), 255, 255, 255 - help.glow);
2020-01-01 21:29:24 +01:00
}
graphics.draw_rect(x, y, 16, 16, graphics.getRGB(255, 164, 255));
break;
}
case 2: // Conveyors & Platforms
{
const int movement = entity->p1;
const short length = (movement == 7 || movement == 8) ? 8 : 4;
const short glow = 255 - help.glow;
for (int j = 0; j < length; j++) {
graphics.draw_grid_tile(custom_gray ? graphics.grphx.im_entcolours_tint : graphics.grphx.im_entcolours, obj.customplatformtile, x + (j * 8), y, 8, 8);
2020-01-01 21:29:24 +01:00
}
switch (movement)
2020-01-01 21:29:24 +01:00
{
case 0:
case 1:
case 2:
case 3:
// If they have a basic movement type, draw an arrow to indicate direction
font::print(PR_FONT_8X8, x + 12, y, std::string(1, arrows[movement]), glow, glow, glow);
break;
case 4:
// Always move right, stopping when hitting collision
font::print(PR_FONT_8X8, x + 8, y, ">I", glow, glow, glow);
break;
case 5:
font::print(PR_FONT_8X8, x, y, ">>>>", glow, glow, glow);
break;
case 6:
font::print(PR_FONT_8X8, x, y, "<<<<", glow, glow, glow);
break;
case 7:
font::print(PR_FONT_8X8, x + 4, y, "> > > > ", glow, glow, glow);
break;
case 8:
font::print(PR_FONT_8X8, x + 4, y, "< < < < ", glow, glow, glow);
break;
2020-01-01 21:29:24 +01:00
}
if (movement < 0)
2020-01-01 21:29:24 +01:00
{
// Well, it's a negative type, so it'll just be still.
font::print(PR_FONT_8X8, x + 8, y, "[]", glow, glow, glow);
2020-01-01 21:29:24 +01:00
}
else if (movement > 8)
2020-01-01 21:29:24 +01:00
{
// Invalid... draw a scary red X
font::print(PR_FONT_8X8, x + 12, y, "X", glow, 0, 0);
2020-01-01 21:29:24 +01:00
}
graphics.draw_rect(x, y, 8 * length, 8, graphics.getRGB(255, 255, 255));
2020-01-01 21:29:24 +01:00
break;
}
case 3: // Disappearing Platforms
for (int j = 0; j < 4; j++) {
graphics.draw_grid_tile(custom_gray ? graphics.grphx.im_entcolours_tint : graphics.grphx.im_entcolours, obj.customplatformtile, x + (j * 8), y, 8, 8);
}
2020-01-01 21:29:24 +01:00
font::print(PR_FONT_8X8, x, y, "////", 255 - help.glow, 255 - help.glow, 255 - help.glow);
graphics.draw_rect(x, y, 32, 8, graphics.getRGB(255, 255, 255));
2020-01-01 21:29:24 +01:00
break;
case 9: // Shiny Trinkets
graphics.draw_sprite(x, y, 22, 196, 196, 196);
graphics.draw_rect(x, y, 16, 16, graphics.getRGB(255, 164, 164));
2020-01-01 21:29:24 +01:00
break;
case 10: // Checkpoints
graphics.draw_sprite(x, y, 20 + entity->p1, 196, 196, 196);
graphics.draw_rect(x, y, 16, 16, graphics.getRGB(255, 164, 164));
2020-01-01 21:29:24 +01:00
break;
case 11: // Gravity Lines
// p2 is in tiles, and p3 is in pixels
if (entity->p1 == 0)
2020-01-01 21:29:24 +01:00
{
// Horizontal gravity line
const int left = entity->p2 * 8;
const int width = entity->p3;
graphics.fill_rect(left, y + 4, width, 1, graphics.getRGB(194, 194, 194));
2020-01-01 21:29:24 +01:00
}
else
2020-01-01 21:29:24 +01:00
{
// Vertical gravity line
const int top = entity->p2 * 8;
const int height = entity->p3;
graphics.fill_rect(x + 3, top, 1, height, graphics.getRGB(194, 194, 194));
2020-01-01 21:29:24 +01:00
}
graphics.draw_rect(x, y, 8, 8, graphics.getRGB(164, 255, 164));
2020-01-01 21:29:24 +01:00
break;
case 13: // Warp Tokens
{
std::string text;
graphics.draw_sprite(x, y, 18 + (ed.entframe % 2), 196, 196, 196);
graphics.draw_rect(x, y, 16, 16, graphics.getRGB(255, 164, 164));
if (i == edent_under_cursor)
2020-01-01 21:29:24 +01:00
{
text = "(" + help.String(entity->p1 / 40 + 1) + "," + help.String(entity->p2 / 30 + 1) + ")";
2020-01-01 21:29:24 +01:00
}
else
{
text = help.String(cl.findwarptoken(i));
2020-01-01 21:29:24 +01:00
}
font::print(PR_BOR | PR_CJK_HIGH, x, y - 8, text, 210, 210, 255);
2020-01-01 21:29:24 +01:00
break;
}
case 15: // Crewmates
graphics.draw_sprite(x - 4, y, 144, graphics.crewcolourreal(entity->p1));
graphics.draw_rect(x, y, 16, 24, graphics.getRGB(164, 164, 164));
2020-01-01 21:29:24 +01:00
break;
case 16: // Start Point
{
const short labelcol = ed.entframe < 2 ? 255 : 196;
if (entity->p1 == 0) // Facing right
2020-01-01 21:29:24 +01:00
{
graphics.draw_sprite(x - 4, y, 0, graphics.col_crewcyan);
2020-01-01 21:29:24 +01:00
}
else // Non-zero is facing left
2020-01-01 21:29:24 +01:00
{
graphics.draw_sprite(x - 4, y, 3, graphics.col_crewcyan);
2020-01-01 21:29:24 +01:00
}
graphics.draw_rect(x, y, 16, 24, graphics.getRGB(255, 255, 164));
font::print(PR_BOR | PR_CEN | PR_CJK_HIGH, x + 8, y - 8, loc::gettext("START"), labelcol, labelcol, labelcol);
2020-01-01 21:29:24 +01:00
break;
}
case 17: // Roomtext
{
int width = 8;
int height = 8;
if (entity->scriptname.length() > 0)
2020-01-01 21:29:24 +01:00
{
width = font::len(PR_FONT_LEVEL, entity->scriptname.c_str());
height = font::height(PR_FONT_LEVEL);
2020-01-01 21:29:24 +01:00
}
graphics.draw_rect(x, y, width, height, graphics.getRGB(96, 96, 96));
font::print(PR_FONT_LEVEL | PR_CJK_LOW, x, y, entity->scriptname, 196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
break;
}
case 18: // Terminals
{
int sprite = entity->p1;
int corrected_y = y;
// Not a boolean: just swapping 0 and 1, leaving the rest alone
if (sprite == 0)
{
sprite = 1; // Unflipped
}
else if (sprite == 1)
{
sprite = 0; // Flipped;
corrected_y -= 8;
}
graphics.draw_sprite(x, corrected_y + 8, sprite + 16, 96, 96, 96);
graphics.draw_rect(x, y, 16, 24, graphics.getRGB(164, 164, 164));
if (i == edent_under_cursor)
2020-01-01 21:29:24 +01:00
{
font::print(PR_FONT_LEVEL | PR_BOR | PR_CJK_HIGH, x, y - 8, entity->scriptname, 210, 210, 255);
2020-01-01 21:29:24 +01:00
}
break;
}
case 19: // Script Triggers
graphics.draw_rect(x, y, entity->p1 * 8, entity->p2 * 8, graphics.getRGB(255, 164, 255));
graphics.draw_rect(x, y, 8, 8, graphics.getRGB(255, 255, 255));
if (i == edent_under_cursor)
2020-01-01 21:29:24 +01:00
{
font::print(PR_FONT_LEVEL | PR_BOR | PR_CJK_HIGH, x, y - 8, entity->scriptname, 210, 210, 255);
2020-01-01 21:29:24 +01:00
}
break;
case 50: // Warp Lines
if (entity->p1 >= 2) // Horizontal
2020-01-01 21:29:24 +01:00
{
int left = entity->p2;
int right = left + entity->p3 / 8;
graphics.draw_rect((left * 8), y + 1, (right - left) * 8, 6, graphics.getRGB(194, 255, 255));
graphics.draw_rect(x, y, 8, 8, graphics.getRGB(164, 255, 255));
2020-01-01 21:29:24 +01:00
}
else // Vertical
2020-01-01 21:29:24 +01:00
{
int top = entity->p2;
int bottom = top + entity->p3 / 8;
graphics.draw_rect(x + 1, (top * 8), 6, (bottom - top) * 8, graphics.getRGB(194, 255, 255));
graphics.draw_rect(x, y, 8, 8, graphics.getRGB(164, 255, 255));
2020-01-01 21:29:24 +01:00
}
break;
}
}
// Need to also check warp point destinations
if (entity->t == 13 && ed.warpent != i)
2020-01-01 21:29:24 +01:00
{
// Is the destination in this room?
if (entity->p1 / 40 == ed.levx && entity->p2 / 30 == ed.levy)
2020-01-01 21:29:24 +01:00
{
const int x = entity->p1 % 40 * 8;
const int y = entity->p2 % 30 * 8;
std::string text;
graphics.draw_sprite(x, y, 18 + (ed.entframe % 2), 64, 64, 64);
graphics.draw_rect((entity->p1 * 8) - (ed.levx * 40 * 8), (entity->p2 * 8) - (ed.levy * 30 * 8), 16, 16, graphics.getRGB(96, 64, 64));
if (ed.tilex == x / 8 && ed.tiley == y / 8)
2020-01-01 21:29:24 +01:00
{
text = "(" + help.String(entity->x / 40 + 1) + "," + help.String(entity->y / 30 + 1) + ")";
2020-01-01 21:29:24 +01:00
}
else
{
text = help.String(cl.findwarptoken(i));
2020-01-01 21:29:24 +01:00
}
font::print(PR_BOR | PR_CJK_HIGH, x, y - 8, text, 190, 190, 225);
2020-01-01 21:29:24 +01:00
}
}
}
}
2020-01-01 21:29:24 +01:00
static void draw_ghosts(void)
{
extern editorclass ed;
//Draw ghosts (spooky!)
if (game.ghostsenabled) {
graphics.set_render_target(graphics.ghostTexture);
graphics.set_blendmode(graphics.ghostTexture, SDL_BLENDMODE_BLEND);
graphics.clear(0, 0, 0, 0);
for (int i = 0; i < (int) ed.ghosts.size(); i++) {
if (i <= ed.currentghosts) { // We don't want all of them to show up at once :)
if (ed.ghosts[i].rx != ed.levx || ed.ghosts[i].ry != ed.levy)
continue;
SDL_Color ct = ed.ghosts[i].realcol;
const int alpha = 3 * ct.a / 4;
ct.a = (Uint8)alpha;
graphics.draw_sprite(ed.ghosts[i].x, ed.ghosts[i].y, ed.ghosts[i].frame, ct);
}
}
graphics.set_render_target(graphics.gameTexture);
graphics.set_texture_alpha_mod(graphics.ghostTexture, 128);
graphics.copy_texture(graphics.ghostTexture, NULL, NULL);
}
}
static void draw_bounds(void)
{
extern editorclass ed;
const RoomProperty* const room = cl.getroomprop(ed.levx, ed.levy);
if (ed.boundarymod>0)
2020-01-01 21:29:24 +01:00
{
if(ed.boundarymod==1)
{
graphics.draw_rect(ed.tilex*8, ed.tiley*8, 8,8,graphics.getRGB(210 + help.glow/2, 191 + help.glow, 255 - help.glow/2));
graphics.draw_rect((ed.tilex*8)+2, (ed.tiley*8)+2, 4,4,graphics.getRGB(105 + help.glow/4, 100 + help.glow/2, 128 - help.glow/4));
2020-01-01 21:29:24 +01:00
}
else if(ed.boundarymod==2)
{
if((ed.tilex*8)+8<=ed.boundx1 || (ed.tiley*8)+8<=ed.boundy1)
{
graphics.draw_rect(ed.boundx1, ed.boundy1, 8, 8,graphics.getRGB(210 + help.glow/2, 191 + help.glow, 255 - help.glow/2));
graphics.draw_rect(ed.boundx1+2, ed.boundy1+2, 4, 4,graphics.getRGB(105 + help.glow/4, 100 + help.glow/2, 128 - help.glow/4));
2020-01-01 21:29:24 +01:00
}
else
{
graphics.draw_rect(ed.boundx1, ed.boundy1, (ed.tilex*8)+8-ed.boundx1,(ed.tiley*8)+8-ed.boundy1,graphics.getRGB(210 + help.glow/2, 191 + help.glow, 255 - help.glow/2));
graphics.draw_rect(ed.boundx1+2, ed.boundy1+2, (ed.tilex*8)+8-ed.boundx1-4,(ed.tiley*8)+8-ed.boundy1-4,graphics.getRGB(105 + help.glow/4, 100 + help.glow/2, 128 - help.glow/4));
2020-01-01 21:29:24 +01:00
}
}
}
else
{
//Draw boundaries
if(room->enemyx1!=0 || room->enemyy1!=0
|| room->enemyx2!=320 || room->enemyy2!=240)
2020-01-01 21:29:24 +01:00
{
graphics.draw_rect( room->enemyx1, room->enemyy1,
room->enemyx2-room->enemyx1,
room->enemyy2-room->enemyy1,
graphics.getRGB(255-(help.glow/2),64,64));
2020-01-01 21:29:24 +01:00
}
if(room->platx1!=0 || room->platy1!=0
|| room->platx2!=320 || room->platy2!=240)
2020-01-01 21:29:24 +01:00
{
graphics.draw_rect( room->platx1, room->platy1,
room->platx2-room->platx1,
room->platy2-room->platy1,
graphics.getRGB(64,64,255-(help.glow/2)));
2020-01-01 21:29:24 +01:00
}
}
}
2020-01-01 21:29:24 +01:00
static void draw_cursor(void)
{
extern editorclass ed;
static const SDL_Color blue = graphics.getRGB(32, 32, 200);
const int x = ed.tilex * 8;
const int y = ed.tiley * 8;
switch (ed.current_tool)
{
case EditorTool_WALLS:
case EditorTool_BACKING:
// Modifiers!
if (ed.bmod) graphics.draw_rect(x, 0, 8, 240, blue); // Vertical
else if (ed.hmod) graphics.draw_rect(0, y, 320, 8, blue); // Horizontal
else if (ed.vmod) graphics.draw_rect(x - 32, y - 32, 24 + 48, 24 + 48, blue); // 9x9
else if (ed.cmod) graphics.draw_rect(x - 24, y - 24, 24 + 32, 24 + 32, blue); // 7x7
else if (ed.xmod) graphics.draw_rect(x - 16, y - 16, 24 + 16, 24 + 16, blue); // 5x5
else if (ed.zmod) graphics.draw_rect(x - 8, y - 8, 24, 24, blue); // 3x3
SDL_FALLTHROUGH;
case EditorTool_SPIKES:
case EditorTool_GRAVITY_LINES:
case EditorTool_ROOMTEXT:
case EditorTool_SCRIPTS:
// 1x1
graphics.draw_rect(x, y, 8, 8, blue);
2020-01-01 21:29:24 +01:00
break;
case EditorTool_TRINKETS:
case EditorTool_CHECKPOINTS:
case EditorTool_ENEMIES:
case EditorTool_WARP_TOKENS:
// 2x2
graphics.draw_rect(x, y, 16, 16, blue);
2020-01-01 21:29:24 +01:00
break;
case EditorTool_DISAPPEARING_PLATFORMS:
case EditorTool_CONVEYORS:
case EditorTool_MOVING_PLATFORMS:
// 1x4 (platforms)
graphics.draw_rect(x, y, 32, 8, blue);
2020-01-01 21:29:24 +01:00
break;
case EditorTool_WARP_LINES:
// 1x1, but X if not on an edge (warp lines)
if (ed.tilex == 0 || ed.tilex == 39 || ed.tiley == 0 || ed.tiley == 29)
2020-01-01 21:29:24 +01:00
{
graphics.draw_rect(x, y, 8, 8, blue);
2020-01-01 21:29:24 +01:00
}
else
{
font::print(PR_FONT_8X8, x, y, "X", 255, 0, 0);
2020-01-01 21:29:24 +01:00
}
break;
case EditorTool_TERMINALS:
case EditorTool_CREWMATES:
case EditorTool_START_POINT:
// 2x3
graphics.draw_rect(x, y, 16, 24, blue);
2020-01-01 21:29:24 +01:00
break;
}
}
static void draw_tile_toolbox(int tileset)
{
extern editorclass ed;
// Tile toolbox for direct mode
int t2 = 0;
if (ed.dmtileeditor > 0)
{
if (ed.dmtileeditor <= 4)
{
t2 = graphics.lerp((4 - ed.dmtileeditor + 1) * 12, (4 - ed.dmtileeditor) * 12);
}
// Draw five lines of the editor
const int temp = ed.dmtile - (ed.dmtile % 40) - 80;
graphics.fill_rect(0, -t2, 320, 40, graphics.getRGB(0, 0, 0));
graphics.fill_rect(0, -t2 + 40, 320, 2, graphics.getRGB(255, 255, 255));
int texturewidth;
int textureheight;
if (graphics.query_texture(graphics.grphx.im_tiles, NULL, NULL, &texturewidth, &textureheight) != 0)
{
return;
}
const int numtiles = (int)(texturewidth / 8) * (textureheight / 8);
for (int x = 0; x < 40; x++)
{
for (int y = 0; y < 5; y++)
{
if (tileset == 0)
{
graphics.drawtile(x * 8, (y * 8) - t2, (temp + numtiles + (y * 40) + x) % numtiles);
}
else
{
graphics.drawtile2(x * 8, (y * 8) - t2, (temp + numtiles + (y * 40) + x) % numtiles);
}
}
}
// Highlight our little block
graphics.draw_rect(((ed.dmtile % 40) * 8) - 2, 16 - t2 - 2, 12, 12, graphics.getRGB(255 - help.glow, 196, 196));
graphics.draw_rect(((ed.dmtile % 40) * 8) - 1, 16 - t2 - 1, 10, 10, graphics.getRGB(0, 0, 0));
}
if (ed.dmtileeditor > 0 && t2 <= 30)
{
short labellen = 2 + font::len(0, loc::gettext("Tile:"));
font::print(PR_BOR, 2, 45 - t2, loc::gettext("Tile:"), 196, 196, 255 - help.glow);
font::print(PR_BOR, labellen + 16, 45 - t2, help.String(ed.dmtile), 196, 196, 255 - help.glow);
graphics.fill_rect(labellen + 2, 44 - t2, 10, 10, graphics.getRGB(255 - help.glow, 196, 196));
graphics.fill_rect(labellen + 3, 45 - t2, 8, 8, graphics.getRGB(0, 0, 0));
if (tileset == 0)
{
graphics.drawtile(labellen + 3, 45 - t2, ed.dmtile);
}
else
{
graphics.drawtile2(labellen + 3, 45 - t2, ed.dmtile);
}
}
else
{
short labellen = 2 + font::len(0, loc::gettext("Tile:"));
int y = 2 + font::height(0);
y = SDL_max(y, 12);
font::print(PR_BOR, 2, y, loc::gettext("Tile:"), 196, 196, 255 - help.glow);
font::print(PR_BOR, labellen + 16, y, help.String(ed.dmtile), 196, 196, 255 - help.glow);
graphics.fill_rect(labellen + 2, y - 1, 10, 10, graphics.getRGB(255 - help.glow, 196, 196));
graphics.fill_rect(labellen + 3, y, 8, 8, graphics.getRGB(0, 0, 0));
if (tileset == 0)
{
graphics.drawtile(labellen + 3, 12, ed.dmtile);
}
else
{
graphics.drawtile2(labellen + 3, 12, ed.dmtile);
}
}
}
void editorrender(void)
{
extern editorclass ed;
const RoomProperty* const room = cl.getroomprop(ed.levx, ed.levy);
2020-01-01 21:29:24 +01:00
graphics.clear();
switch (ed.state)
2020-01-01 21:29:24 +01:00
{
case EditorState_DRAW:
// Draw the editor guidelines
draw_background_grid();
// Draw the background, if any, over the guidelines
if (!ed.settingsmod)
{
draw_background(room->warpdir);
}
graphics.drawmap();
draw_edgeguides();
draw_entities();
draw_ghosts();
draw_bounds();
if (room->directmode == 1)
2020-01-01 21:29:24 +01:00
{
draw_tile_toolbox(room->tileset);
2020-01-01 21:29:24 +01:00
}
draw_cursor();
break;
2020-01-01 21:29:24 +01:00
}
// Draw GUI
2020-01-01 21:29:24 +01:00
if(ed.boundarymod>0)
{
std::string message;
2020-01-01 21:29:24 +01:00
if(ed.boundarymod==1)
{
switch(ed.boundarytype)
{
case 0:
message = loc::gettext("SCRIPT BOX: Click on top left");
2020-01-01 21:29:24 +01:00
break;
case 1:
message = loc::gettext("ENEMY BOUNDS: Click on top left");
2020-01-01 21:29:24 +01:00
break;
case 2:
message = loc::gettext("PLATFORM BOUNDS: Click on top left");
2020-01-01 21:29:24 +01:00
break;
default:
message = loc::gettext("Click on top left");
2020-01-01 21:29:24 +01:00
break;
}
}
else if(ed.boundarymod==2)
{
switch(ed.boundarytype)
{
case 0:
message = loc::gettext("SCRIPT BOX: Click on bottom right");
2020-01-01 21:29:24 +01:00
break;
case 1:
message = loc::gettext("ENEMY BOUNDS: Click on bottom right");
2020-01-01 21:29:24 +01:00
break;
case 2:
message = loc::gettext("PLATFORM BOUNDS: Click on bottom right");
2020-01-01 21:29:24 +01:00
break;
default:
message = loc::gettext("Click on bottom right");
2020-01-01 21:29:24 +01:00
break;
}
}
short lines;
message = font::string_wordwrap(0, message, 312, &lines);
short textheight = font::height(0)*lines;
graphics.fill_rect(0,238-textheight,320,240, graphics.getRGB(32,32,32));
graphics.fill_rect(0,239-textheight,320,240, graphics.getRGB(0,0,0));
font::print_wrap(0, 4, 240-textheight, message.c_str(), 255,255,255, 8, 312);
2020-01-01 21:29:24 +01:00
}
else if(ed.scripteditmod)
{
//Elaborate C64 BASIC menu goes here!
graphics.fill_rect(0,0,320,240, graphics.getRGB(123, 111, 218));
graphics.fill_rect(14,16,292,208, graphics.getRGB(61, 48, 162));
2020-01-01 21:29:24 +01:00
switch(ed.scripthelppage)
{
case 0:
font::print(PR_CEN, -1,28,loc::gettext("**** VVVVVV SCRIPT EDITOR ****"), 123, 111, 218);
font::print(PR_CEN, -1,44,loc::gettext("PRESS ESC TO RETURN TO MENU"), 123, 111, 218);
2020-01-01 21:29:24 +01:00
Make `commands`, `sb`, and `hooklist` not use separate length-trackers This is a refactor that turns the script-related arrays `ed.sb`, and `ed.hooklist` into C++ vectors (`script.commands` was already a vector, it was just misused). The code handling these vectors now looks more like idiomatic C++ than sloppily-pasted pseudo-ActionScript. This removes the variables `script.scriptlength`, `ed.sblength`, and `ed.numhooks`, too. This reduces the amount of code needed to e.g. simply remove something from any of these vectors. Previously the code had to manually shift the rest of the elements down one-by-one, and doing it manually is definitely error-prone and tedious. But now we can just use fancy functions like `std::vector::erase()` and `std::remove()` to do it all in one line! Don't worry, I checked and `std::remove()` is in the C++ standard since at least 1998. This patch makes it so the `commands` vector gets cleared when `scriptclass::load()` is ran. Previously, the `commands` vector never actually properly got cleared, so there could potentially be glitches that rely on the game indexing past the bounds set by `scriptlength` but still in-bounds in the eyes of C++, and people could potentially rely on such an exploit... However, I checked, and I'm pretty sure that no such glitch previously existed at all, because the only times the vector gets indexed are when `scriptlength` is either being incremented after starting from 0 (`add()`) or when it's underneath a `position < scriptlength` conditional. Furthermore, I'm unaware of anyone who has actually found or used such an exploit, and I've been in the custom level community for 6 years. So I think it's fine.
2020-02-20 18:43:52 +01:00
if(!ed.hooklist.empty())
2020-01-01 21:29:24 +01:00
{
for(int i=0; i<9; i++)
{
Make `commands`, `sb`, and `hooklist` not use separate length-trackers This is a refactor that turns the script-related arrays `ed.sb`, and `ed.hooklist` into C++ vectors (`script.commands` was already a vector, it was just misused). The code handling these vectors now looks more like idiomatic C++ than sloppily-pasted pseudo-ActionScript. This removes the variables `script.scriptlength`, `ed.sblength`, and `ed.numhooks`, too. This reduces the amount of code needed to e.g. simply remove something from any of these vectors. Previously the code had to manually shift the rest of the elements down one-by-one, and doing it manually is definitely error-prone and tedious. But now we can just use fancy functions like `std::vector::erase()` and `std::remove()` to do it all in one line! Don't worry, I checked and `std::remove()` is in the C++ standard since at least 1998. This patch makes it so the `commands` vector gets cleared when `scriptclass::load()` is ran. Previously, the `commands` vector never actually properly got cleared, so there could potentially be glitches that rely on the game indexing past the bounds set by `scriptlength` but still in-bounds in the eyes of C++, and people could potentially rely on such an exploit... However, I checked, and I'm pretty sure that no such glitch previously existed at all, because the only times the vector gets indexed are when `scriptlength` is either being incremented after starting from 0 (`add()`) or when it's underneath a `position < scriptlength` conditional. Furthermore, I'm unaware of anyone who has actually found or used such an exploit, and I've been in the custom level community for 6 years. So I think it's fine.
2020-02-20 18:43:52 +01:00
if(ed.hookmenupage+i<(int)ed.hooklist.size())
2020-01-01 21:29:24 +01:00
{
if(ed.hookmenupage+i==ed.hookmenu)
{
std::string text_upper(loc::toupper(ed.hooklist[(ed.hooklist.size()-1)-(ed.hookmenupage+i)]));
char buffer[SCREEN_WIDTH_CHARS + 1];
vformat_buf(buffer, sizeof(buffer), loc::get_langmeta()->menu_select.c_str(), "label:str", text_upper.c_str());
font::print(PR_CEN, -1, 68+(i*16), buffer, 123, 111, 218);
2020-01-01 21:29:24 +01:00
}
else
{
font::print(PR_CEN, -1, 68+(i*16), ed.hooklist[(ed.hooklist.size()-1)-(ed.hookmenupage+i)], 123, 111, 218);
2020-01-01 21:29:24 +01:00
}
}
}
}
else
{
font::print(PR_CEN, -1,110,loc::gettext("NO SCRIPT IDS FOUND"), 123, 111, 218);
font::print_wrap(PR_CEN, -1,130,loc::gettext("CREATE A SCRIPT WITH EITHER THE TERMINAL OR SCRIPT BOX TOOLS"), 123, 111, 218, 10, 288);
2020-01-01 21:29:24 +01:00
}
break;
case 1:
{
2020-01-01 21:29:24 +01:00
//Current scriptname
graphics.fill_rect(14,226,292,12, graphics.getRGB(61, 48, 162));
char namebuffer[SCREEN_WIDTH_CHARS + 1];
vformat_buf(
namebuffer, sizeof(namebuffer),
loc::gettext("CURRENT SCRIPT: {name}"),
"name:str",
ed.sbscript.c_str()
);
font::print(PR_CEN, -1,228, namebuffer, 123, 111, 218);
2020-01-01 21:29:24 +01:00
//Draw text
int font_height = font::height(PR_FONT_LEVEL);
for(int i=0; i<ed.lines_visible; i++)
2020-01-01 21:29:24 +01:00
{
Make `commands`, `sb`, and `hooklist` not use separate length-trackers This is a refactor that turns the script-related arrays `ed.sb`, and `ed.hooklist` into C++ vectors (`script.commands` was already a vector, it was just misused). The code handling these vectors now looks more like idiomatic C++ than sloppily-pasted pseudo-ActionScript. This removes the variables `script.scriptlength`, `ed.sblength`, and `ed.numhooks`, too. This reduces the amount of code needed to e.g. simply remove something from any of these vectors. Previously the code had to manually shift the rest of the elements down one-by-one, and doing it manually is definitely error-prone and tedious. But now we can just use fancy functions like `std::vector::erase()` and `std::remove()` to do it all in one line! Don't worry, I checked and `std::remove()` is in the C++ standard since at least 1998. This patch makes it so the `commands` vector gets cleared when `scriptclass::load()` is ran. Previously, the `commands` vector never actually properly got cleared, so there could potentially be glitches that rely on the game indexing past the bounds set by `scriptlength` but still in-bounds in the eyes of C++, and people could potentially rely on such an exploit... However, I checked, and I'm pretty sure that no such glitch previously existed at all, because the only times the vector gets indexed are when `scriptlength` is either being incremented after starting from 0 (`add()`) or when it's underneath a `position < scriptlength` conditional. Furthermore, I'm unaware of anyone who has actually found or used such an exploit, and I've been in the custom level community for 6 years. So I think it's fine.
2020-02-20 18:43:52 +01:00
if(i+ed.pagey<(int)ed.sb.size())
2020-01-01 21:29:24 +01:00
{
font::print(PR_FONT_LEVEL | PR_CJK_LOW, 16, 20+(i*font_height), ed.sb[i+ed.pagey], 123, 111, 218);
2020-01-01 21:29:24 +01:00
}
}
//Draw cursor
if(ed.entframe<2)
{
font::print(PR_FONT_LEVEL | PR_CJK_LOW, 16+font::len(PR_FONT_LEVEL, ed.sb[ed.pagey+ed.sby].c_str()),20+(ed.sby*font_height),"_",123, 111, 218);
2020-01-01 21:29:24 +01:00
}
break;
}
}
2020-01-01 21:29:24 +01:00
}
else if(ed.settingsmod)
{
if(!game.colourblindmode)
{
graphics.drawtowerbackground(graphics.titlebg);
}
else
{
graphics.clear();
}
2020-01-01 21:29:24 +01:00
int tr = graphics.titlebg.r - (help.glow / 4) - int(fRandom() * 4);
int tg = graphics.titlebg.g - (help.glow / 4) - int(fRandom() * 4);
int tb = graphics.titlebg.b - (help.glow / 4) - int(fRandom() * 4);
2020-01-01 21:29:24 +01:00
if (tr < 0) tr = 0;
if (tr > 255) tr = 255;
2020-01-01 21:29:24 +01:00
if (tg < 0) tg = 0;
if (tg > 255) tg = 255;
2020-01-01 21:29:24 +01:00
if (tb < 0) tb = 0;
if (tb > 255) tb = 255;
editormenurender(tr, tg, tb);
2020-01-01 21:29:24 +01:00
graphics.drawmenu(tr, tg, tb, game.currentmenuname);
2020-01-01 21:29:24 +01:00
}
else if (ed.textmod)
2020-01-01 21:29:24 +01:00
{
short lines;
std::string wrapped = font::string_wordwrap(0, ed.textdesc, 312, &lines);
short textheight = font::height(0)*lines+font::height(PR_FONT_LEVEL);
graphics.fill_rect(0, 238-textheight, 320, 240, graphics.getRGB(32, 32, 32));
graphics.fill_rect(0, 239-textheight, 320, 240, graphics.getRGB(0, 0, 0));
font::print_wrap(0, 4, 240-textheight, wrapped.c_str(), 255, 255, 255, 8, 312);
std::string input = key.keybuffer;
if (ed.entframe < 2)
2020-01-01 21:29:24 +01:00
{
input += "_";
2020-01-01 21:29:24 +01:00
}
else
{
input += " ";
2020-01-01 21:29:24 +01:00
}
font::print(PR_CEN | PR_FONT_LEVEL | PR_CJK_HIGH, -1, 232, input, 196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
}
else if(ed.warpmod)
{
//placing warp token
int textheight = font::height(0);
graphics.fill_rect(0,237-textheight*2,320,240, graphics.getRGB(32,32,32));
graphics.fill_rect(0,238-textheight*2,320,240, graphics.getRGB(0,0,0));
font::print(PR_CJK_LOW, 4, 240-textheight*2, loc::gettext("Left click to place warp destination"), 196, 196, 255 - help.glow);
font::print(PR_CJK_LOW, 4, 240-textheight, loc::gettext("Right click to cancel"), 196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
}
else
{
char coords[8];
SDL_snprintf(coords, sizeof(coords), "(%d,%d)", ed.levx+1, ed.levy+1);
2020-01-01 21:29:24 +01:00
if(ed.spacemod)
{
graphics.fill_rect(0,207,320,240, graphics.getRGB(32,32,32));
graphics.fill_rect(0,208,320,240, graphics.getRGB(0,0,0));
2020-01-01 21:29:24 +01:00
// Draw tool icons!
2020-01-01 21:29:24 +01:00
int tx = 6;
const int ty = 210;
const int tg = 32;
2020-01-01 21:29:24 +01:00
const int page = ed.current_tool / 10;
const int max_pages = SDL_ceil(NUM_EditorTools / 10);
const int page_tool_count = SDL_min(10, NUM_EditorTools - (page * 10));
for (int i = 0; i < page_tool_count; i++)
2020-01-01 21:29:24 +01:00
{
const int current_tool_id = i + (page * 10);
Fix editor tool menu inconsistencies This patch de-duplicates the tool drawing code a bit in the menu that gets brought up when you press Space in the level editor, as well as fixes several bugs related to the fact that the original author(s) of the code decided to copy-paste everything. (It was most likely Terry, judging by the distinct lack of whitespace between tokens in the code.) There are two "pages" of tools that get shown when you open the tool menu, according to your currently-selected tool. 1. On the first page, your currently-selected tool gets a brighter outline. However, on the second page, the code to draw the outline over your currently-selected tool is missing. So I've fixed that. 2. On the first page, the glyph indicator next to the tool icon also gets brighter when you have that tool selected. However, on the second page, the code that drew the brighter-colored indicator got ran before the code that drew the normal-colored indicator, so this was never shown. This is also fixed. 3. The glyph indicator of the gravity line tool didn't get brighter when you had it selected, due to its special-cased copy-pasted code drawing its brighter color before drawing its normal color. This has also been fixed. 4. Lastly, the tool menu no longer draws the brighter-colored glyphs on top of the normal-colored glyphs. Instead, the menu will simply draw the brighter-colored glyphs and will not draw the normal-colored glyphs in the first place. This is because double-drawing text like this will look bad if the user has a custom font.png that has translucent pixels, like I do. All of these bugs have been fixed by paying off the technical debt of copy-pasting code.
2021-01-11 06:10:03 +01:00
// First, draw the background
graphics.fill_rect(4 + (i * tg), 208, 20, 20, graphics.getRGB(32, 32, 32));
// Draw the actual tool icon
ed.draw_tool((EditorTools) current_tool_id, 4 + (i * tg) + 2, 208 + 2);
// Draw the tool outline...
graphics.draw_rect(4 + (i * tg), 208, 20, 20, (current_tool_id == ed.current_tool) ? graphics.getRGB(200, 200, 200) : graphics.getRGB(96, 96, 96));
// ...and the hotkey
const int col = current_tool_id == ed.current_tool ? 255 : 164;
font::print(PR_FONT_8X8 | PR_BOR, 22 + i * tg - 4, 224 - 4, ed.toolkeychar[current_tool_id], col, col, col);
2020-01-01 21:29:24 +01:00
}
// Draw the page number, limit is 1 digit, so the max is 9 pages
char buffer[4];
SDL_snprintf(buffer, sizeof(buffer), "%d/%d", page + 1, max_pages + 1);
font::print(PR_CJK_HIGH, 4, 232, buffer, 196, 196, 255 - help.glow);
// Draw the button hint text
char changetooltext[SCREEN_WIDTH_CHARS + 1];
vformat_buf(changetooltext, sizeof(changetooltext),
loc::gettext("{button1} and {button2} keys change tool"),
"button1:str, button2:str",
",", "."
);
font::print(PR_CJK_HIGH | PR_RIGHT, 320, 232, changetooltext, 196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
// Draw the current tool name
char toolname[SCREEN_WIDTH_CHARS + 1];
SDL_snprintf(toolname, sizeof(toolname), "%s: %s", ed.toolkeychar[ed.current_tool], loc::gettext(ed.toolnames[ed.current_tool]));
int bgheight = 2 + font::height(0);
int toolnamelen = font::len(0, toolname);
graphics.fill_rect(0,206-bgheight,toolnamelen+8,bgheight+1, graphics.getRGB(32,32,32));
graphics.fill_rect(0,207-bgheight,toolnamelen+7,bgheight, graphics.getRGB(0,0,0));
font::print(PR_BOR | PR_CJK_HIGH, 2,198, toolname, 196, 196, 255 - help.glow);
int coordslen = font::len(0, coords);
graphics.fill_rect(319-coordslen-8,206-bgheight,coordslen+8,bgheight+1, graphics.getRGB(32,32,32));
graphics.fill_rect(320-coordslen-8,207-bgheight,coordslen+8,bgheight, graphics.getRGB(0,0,0));
font::print(PR_BOR | PR_CJK_HIGH | PR_RIGHT, 316, 198, coords, 196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
}
else
{
//graphics.fill_rect(0,230,72,240, graphics.RGB(32,32,32));
//graphics.fill_rect(0,231,71,240, graphics.RGB(0,0,0));
if(room->roomname!="")
2020-01-01 21:29:24 +01:00
{
int font_height = font::height(PR_FONT_LEVEL);
if (font_height <= 8)
{
graphics.footerrect.h = font_height + 2;
}
else
{
graphics.footerrect.h = font_height + 1;
}
graphics.footerrect.y = 240 - graphics.footerrect.h + ed.roomnamehide;
graphics.set_blendmode(SDL_BLENDMODE_BLEND);
graphics.fill_rect(&graphics.footerrect, graphics.getRGBA(0, 0, 0, graphics.translucentroomname ? 127 : 255));
graphics.set_blendmode(SDL_BLENDMODE_NONE);
font::print(PR_CEN | PR_BOR | PR_FONT_LEVEL | PR_CJK_LOW, -1, graphics.footerrect.y+1+ed.roomnamehide, room->roomname, 196, 196, 255 - help.glow);
font::print(PR_BOR | PR_CJK_HIGH, 4, 232-graphics.footerrect.h, loc::gettext("SPACE ^ SHIFT ^"), 196, 196, 255 - help.glow);
font::print(PR_BOR | PR_CJK_HIGH | PR_RIGHT, 316, 232-graphics.footerrect.h, coords, 196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
}
else
{
font::print(PR_BOR | PR_CJK_HIGH, 4, 232, loc::gettext("SPACE ^ SHIFT ^"), 196, 196, 255 - help.glow);
font::print(PR_BOR | PR_CJK_HIGH | PR_RIGHT, 316, 232, coords, 196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
}
}
if(ed.shiftmenu)
{
const char* shiftmenuoptions[] = {
loc::gettext("F1: Change Tileset"),
loc::gettext("F2: Change Colour"),
loc::gettext("F3: Change Enemies"),
loc::gettext("F4: Enemy Bounds"),
loc::gettext("F5: Platform Bounds"),
"",
loc::gettext("F9: Reload Resources"),
loc::gettext("F10: Direct Mode"),
"",
loc::gettext("W: Change Warp Dir"),
loc::gettext("E: Change Roomname"),
};
int menuwidth = 0;
for (size_t i = 0; i < SDL_arraysize(shiftmenuoptions); i++)
{
int len = font::len(0, shiftmenuoptions[i]);
if (len > menuwidth)
menuwidth = len;
}
int lineheight = font::height(0);
lineheight = SDL_max(10, lineheight);
int left_y = 230-SDL_arraysize(shiftmenuoptions)*lineheight;
graphics.draw_rect(0, left_y-3, menuwidth+17, 240,graphics.getRGB(64,64,64));
graphics.fill_rect(0,left_y-2,menuwidth+16,240, graphics.getRGB(0,0,0));
for (size_t i = 0; i < SDL_arraysize(shiftmenuoptions); i++)
font::print(0, 4, left_y+i*lineheight, shiftmenuoptions[i], 164,164,164);
graphics.draw_rect(220, 207,100,60,graphics.getRGB(64,64,64));
graphics.fill_rect(221,208,160,60, graphics.getRGB(0,0,0));
font::print(0, 224, 210, loc::gettext("S: Save Map"),164,164,164);
font::print(0, 224, 210+lineheight, loc::gettext("L: Load Map"),164,164,164);
2020-01-01 21:29:24 +01:00
}
}
if(!ed.settingsmod && !ed.scripteditmod)
{
// Draw the current tool name
char toolname[SCREEN_WIDTH_CHARS + 1];
SDL_snprintf(toolname, sizeof(toolname), "%s: %s", ed.toolkeychar[ed.current_tool], loc::gettext(ed.toolnames[ed.current_tool]));
font::print(PR_BOR, 2, 2, toolname, 196, 196, 255 - help.glow);
2020-01-01 21:29:24 +01:00
}
if(ed.notedelay>0 || ed.oldnotedelay>0)
2020-01-01 21:29:24 +01:00
{
short lines;
std::string wrapped = font::string_wordwrap(0, ed.note, 304, &lines);
short textheight = 8+(lines-1)*SDL_max(10, font::height(0));
short banner_y = 120 - textheight/2 - 5;
float alpha = graphics.lerp(ed.oldnotedelay, ed.notedelay);
graphics.fill_rect(0, banner_y, 320, 10+textheight, graphics.getRGB(92,92,92));
graphics.fill_rect(0, banner_y+1, 320, 8+textheight, graphics.getRGB(0,0,0));
font::print_wrap(PR_CEN, -1,banner_y+5, wrapped.c_str(), 196-((45.0f-alpha)*4), 196-((45.0f-alpha)*4), 196-((45.0f-alpha)*4));
2020-01-01 21:29:24 +01:00
}
graphics.drawfade();
2020-01-01 21:29:24 +01:00
graphics.render();
2020-01-01 21:29:24 +01:00
}
void editorrenderfixed(void)
2020-01-01 21:29:24 +01:00
{
extern editorclass ed;
const RoomProperty* const room = cl.getroomprop(ed.levx, ed.levy);
graphics.updatetitlecolours();
game.customcol=cl.getlevelcol(room->tileset, room->tilecol)+1;
ed.entcol=cl.getenemycol(game.customcol);
ed.entcolreal = graphics.getcol(ed.entcol);
2020-01-01 21:29:24 +01:00
if (game.ghostsenabled)
{
for (size_t i = 0; i < ed.ghosts.size(); i++)
{
GhostInfo& ghost = ed.ghosts[i];
if ((int) i > ed.currentghosts || ghost.rx != ed.levx || ghost.ry != ed.levy)
{
continue;
}
ghost.realcol = graphics.getcol(ghost.col);
}
ed.currentghosts++;
if (ed.zmod)
{
ed.currentghosts++;
}
ed.currentghosts = SDL_min(ed.currentghosts, ed.ghosts.size());
}
if (!ed.settingsmod)
{
switch(room->warpdir)
{
case 1:
graphics.rcol=cl.getwarpbackground(ed.levx, ed.levy);
graphics.updatebackground(3);
break;
case 2:
graphics.rcol=cl.getwarpbackground(ed.levx, ed.levy);
graphics.updatebackground(4);
break;
case 3:
graphics.rcol=cl.getwarpbackground(ed.levx, ed.levy);
graphics.updatebackground(5);
break;
default:
break;
}
}
else if (!game.colourblindmode)
{
graphics.updatetowerbackground(graphics.titlebg);
}
if (cl.getroomprop(ed.levx, ed.levy)->directmode == 1)
{
if (ed.dmtileeditor > 0)
{
ed.dmtileeditor--;
}
}
else
{
ed.dmtileeditor = 0;
}
if (cl.getroomprop(ed.levx, ed.levy)->roomname != "")
{
if (ed.tiley < 28)
{
if (ed.roomnamehide > 0)
{
ed.roomnamehide--;
}
}
else
{
if (ed.roomnamehide < 14)
{
ed.roomnamehide++;
}
}
}
else
{
if (ed.tiley < 28)
{
ed.roomnamehide = 0;
}
else
{
ed.roomnamehide = 14;
}
}
}
void editorlogic(void)
{
extern editorclass ed;
//Misc
help.updateglow();
graphics.titlebg.bypos -= 2;
graphics.titlebg.bscroll = -2;
ed.entframedelay--;
if (ed.entframedelay <= 0)
{
ed.entframe = (ed.entframe + 1) % 4;
ed.entframedelay = 8;
}
ed.oldnotedelay = ed.notedelay;
ed.notedelay = SDL_max(ed.notedelay - 1, 0);
update_entities();
Enumify all fade modes This removes the magic numbers previously used for controlling the fade mode, which are really not readable at all unless you already know what they mean. 0: FADE_NONE 1: FADE_FULLY_BLACK 2: FADE_START_FADEOUT 3: FADE_FADING_OUT 4: FADE_START_FADEIN 5: FADE_FADING_IN There is also the macro FADEMODE_IS_FADING, which indicates when the intention is to only check if the game is fading right now, which wasn't clearly conveyed previously. I also took the opportunity to clean up the style of any lines I touched. This included rewriting if-else chains into case-switches, turning one-liner if-then statements into proper blocks, fixing up comments, and even commenting the `fademode == FADE_NONE` on the tower spike checks (which, it was previously undocumented why that check was there, but I think I know why it's there). As for type safety, we already get some by transforming the variable types into the enum. Assignment is prohibited without a cast. But, apparently, comparison is perfectly legal and won't even give so much as a warning. To work around this and make absolutely sure I made all existing comparisons now use the enum, I temporarily changed it to be an `enum class`, which is a C++11 feature that makes it so all comparisons are illegal. Unfortunately, it scopes them in a namespace with the same name as a class, so I had to temporarily define macros to make sure my existing code worked. I also had to temporarily up the standard in CMakeLists.txt to get it to compile. But after all that was done, I found the rest of the places where a comparison to an integer was used, and fixed them.
2022-04-25 09:57:47 +02:00
if (graphics.fademode == FADE_FULLY_BLACK)
2020-01-01 21:29:24 +01:00
{
//Return to game
graphics.titlebg.colstate = 10;
2020-01-01 21:29:24 +01:00
map.nexttowercolour();
Clean up all exit paths to the menu to use common code There are multiple different exit paths to the main menu. In 2.2, they all had a bunch of copy-pasted code. In 2.3 currently, most of them use game.quittomenu(), but there are some stragglers that still use hand-copied code. This is a bit of a problem, because all exit paths should consistently have FILESYSTEM_unmountassets(), as part of the 2.3 feature of per-level custom assets. Furthermore, most (but not all) of the paths call script.hardreset() too, and some of the stragglers don't. So there could be something persisting through to the title screen (like a really long flash/shake timer) that could only persist if exiting to the title screen through those paths. But, actually, it seems like there's a good reason for some of those to not call script.hardreset() - namely, dying or completing No Death Mode and completing a Time Trial presents some information onscreen that would get reset by script.hardreset(), so I'll fix that in a later commit. So what I've done for this commit is found every exit path that didn't already use game.quittomenu(), and made them use game.quittomenu(). As well, some of them had special handling that existed on top of them already having a corresponding entry in game.quittomenu() (but the path would take the special handling because it never did game.quittomenu()), so I removed that special handling as well (e.g. exiting from a custom level used returntomenu(Menu::levellist) when quittomenu() already had that same returntomenu()). The menu that exiting from the level editor returns to is now handled in game.quittomenu() as well, where the map.custommode branch now also checks for map.custommodeforreal. Unfortunately, it seems like entering the level editor doesn't properly initialize map.custommode, so entering the level editor now initializes map.custommode, too. I've also taken the music.play(6) out of game.quittomenu(), because not all exit paths immediately play Presenting VVVVVV, so all exit paths that DO immediately play Presenting VVVVVV now have music.play(6) special-cased for them, which is fine enough for me. Here is the list of all exit paths to the menu: - Exiting through the pause menu (without glitchrunner mode) - Exiting through the pause menu (with glitchrunner mode) - Completing a custom level - Completing a Time Trial - Dying in No Death Mode - Completing No Death Mode - Completing an Intermission replay - Exiting from the level editor - Completing the main game
2021-01-07 23:20:37 +01:00
game.quittomenu();
music.play(6); //should be before game.quittomenu()
2020-01-01 21:29:24 +01:00
ed.settingsmod=false;
}
}
static void creategameoptions(void)
{
game.createmenu(Menu::options);
}
static void nextbgcolor(void)
{
map.nexttowercolour();
}
2020-01-01 21:29:24 +01:00
static void editormenuactionpress(void)
{
extern editorclass ed;
switch (game.currentmenuname)
{
case Menu::ed_desc:
switch (game.currentmenuoption)
{
case 0:
{
bool title_is_gettext;
translate_title(cl.title, &title_is_gettext);
ed.titlemod=true;
key.enabletextentry();
if (title_is_gettext)
{
key.keybuffer="";
}
else
{
key.keybuffer = cl.title;
}
break;
}
case 1:
{
bool creator_is_gettext;
translate_creator(cl.creator, &creator_is_gettext);
ed.creatormod=true;
key.enabletextentry();
if (creator_is_gettext)
{
key.keybuffer="";
}
else
{
key.keybuffer = cl.creator;
}
break;
}
case 2:
ed.desc1mod=true;
key.enabletextentry();
key.keybuffer=cl.Desc1;
break;
case 3:
ed.websitemod=true;
key.enabletextentry();
key.keybuffer=cl.website;
break;
case 4:
game.createmenu(Menu::ed_font);
map.nexttowercolour();
break;
case 5:
game.returnmenu();
map.nexttowercolour();
break;
}
music.playef(11);
break;
case Menu::ed_settings:
switch (game.currentmenuoption)
{
case 0:
//Change level description stuff
music.playef(11);
game.createmenu(Menu::ed_desc);
map.nexttowercolour();
break;
case 1:
//Enter script editormode
music.playef(11);
ed.scripteditmod=true;
ed.clearscriptbuffer();
key.keybuffer="";
ed.hookmenupage=0;
ed.hookmenu=0;
ed.scripthelppage=0;
ed.scripthelppagedelay=0;
ed.sby=0;
ed.sbx=0, ed.pagey=0;
ed.lines_visible = 200/font::height(PR_FONT_LEVEL);
break;
case 2:
music.playef(11);
game.createmenu(Menu::ed_music);
map.nexttowercolour();
if(cl.levmusic>0) music.play(cl.levmusic);
break;
case 3:
music.playef(11);
game.ghostsenabled = !game.ghostsenabled;
break;
case 4:
//Load level
ed.settingsmod = false;
map.nexttowercolour();
ed.keydelay = 6;
ed.getlin(TEXT_LOAD, loc::gettext("Enter map filename to load:"), &(ed.filename));
game.mapheld = true;
graphics.backgrounddrawn = false;
break;
case 5:
//Save level
ed.settingsmod=false;
map.nexttowercolour();
ed.keydelay = 6;
ed.getlin(TEXT_SAVE, loc::gettext("Enter map filename to save as:"), &(ed.filename));
game.mapheld = true;
graphics.backgrounddrawn = false;
break;
case 6:
/* Game options */
music.playef(11);
game.gamestate = TITLEMODE;
game.ingame_titlemode = true;
game.ingame_editormode = true;
DEFER_CALLBACK(creategameoptions);
DEFER_CALLBACK(nextbgcolor);
break;
default:
music.playef(11);
game.createmenu(Menu::ed_quit);
map.nexttowercolour();
break;
}
break;
case Menu::ed_music:
switch (game.currentmenuoption)
{
case 0:
case 1:
switch (game.currentmenuoption)
{
case 0:
cl.levmusic++;
break;
case 1:
cl.levmusic--;
break;
}
cl.levmusic = POS_MOD(cl.levmusic, 16);
if (cl.levmusic > 0)
{
music.play(cl.levmusic);
}
else
{
music.haltdasmusik();
}
music.playef(11);
break;
case 2:
music.playef(11);
music.fadeout();
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::ed_quit:
switch (game.currentmenuoption)
{
case 0:
//Saving and quit
ed.saveandquit = true;
ed.settingsmod = false;
map.nexttowercolour();
ed.keydelay = 6;
ed.getlin(TEXT_SAVE, loc::gettext("Enter map filename to save as:"), &(ed.filename));
game.mapheld = true;
graphics.backgrounddrawn = false;
break;
case 1:
//Quit without saving
music.playef(11);
music.fadeout();
Enumify all fade modes This removes the magic numbers previously used for controlling the fade mode, which are really not readable at all unless you already know what they mean. 0: FADE_NONE 1: FADE_FULLY_BLACK 2: FADE_START_FADEOUT 3: FADE_FADING_OUT 4: FADE_START_FADEIN 5: FADE_FADING_IN There is also the macro FADEMODE_IS_FADING, which indicates when the intention is to only check if the game is fading right now, which wasn't clearly conveyed previously. I also took the opportunity to clean up the style of any lines I touched. This included rewriting if-else chains into case-switches, turning one-liner if-then statements into proper blocks, fixing up comments, and even commenting the `fademode == FADE_NONE` on the tower spike checks (which, it was previously undocumented why that check was there, but I think I know why it's there). As for type safety, we already get some by transforming the variable types into the enum. Assignment is prohibited without a cast. But, apparently, comparison is perfectly legal and won't even give so much as a warning. To work around this and make absolutely sure I made all existing comparisons now use the enum, I temporarily changed it to be an `enum class`, which is a C++11 feature that makes it so all comparisons are illegal. Unfortunately, it scopes them in a namespace with the same name as a class, so I had to temporarily define macros to make sure my existing code worked. I also had to temporarily up the standard in CMakeLists.txt to get it to compile. But after all that was done, I found the rest of the places where a comparison to an integer was used, and fixed them.
2022-04-25 09:57:47 +02:00
graphics.fademode = FADE_START_FADEOUT;
graphics.backgrounddrawn = false;
break;
case 2:
//Go back to editor
music.playef(11);
game.returnmenu();
map.nexttowercolour();
break;
}
break;
case Menu::ed_font:
{
uint8_t idx_selected = font::font_idx_options[game.currentmenuoption];
cl.level_font_name = font::get_main_font_name(idx_selected);
if (idx_selected == loc::get_langmeta()->font_idx)
{
loc::new_level_font = "";
}
else
{
loc::new_level_font = cl.level_font_name;
}
font::set_level_font(cl.level_font_name.c_str());
music.playef(11);
game.returnmenu();
map.nexttowercolour();
game.savestatsandsettings_menu();
break;
}
default:
break;
}
}
void editorinput(void)
2020-01-01 21:29:24 +01:00
{
extern editorclass ed;
Enumify all fade modes This removes the magic numbers previously used for controlling the fade mode, which are really not readable at all unless you already know what they mean. 0: FADE_NONE 1: FADE_FULLY_BLACK 2: FADE_START_FADEOUT 3: FADE_FADING_OUT 4: FADE_START_FADEIN 5: FADE_FADING_IN There is also the macro FADEMODE_IS_FADING, which indicates when the intention is to only check if the game is fading right now, which wasn't clearly conveyed previously. I also took the opportunity to clean up the style of any lines I touched. This included rewriting if-else chains into case-switches, turning one-liner if-then statements into proper blocks, fixing up comments, and even commenting the `fademode == FADE_NONE` on the tower spike checks (which, it was previously undocumented why that check was there, but I think I know why it's there). As for type safety, we already get some by transforming the variable types into the enum. Assignment is prohibited without a cast. But, apparently, comparison is perfectly legal and won't even give so much as a warning. To work around this and make absolutely sure I made all existing comparisons now use the enum, I temporarily changed it to be an `enum class`, which is a C++11 feature that makes it so all comparisons are illegal. Unfortunately, it scopes them in a namespace with the same name as a class, so I had to temporarily define macros to make sure my existing code worked. I also had to temporarily up the standard in CMakeLists.txt to get it to compile. But after all that was done, I found the rest of the places where a comparison to an integer was used, and fixed them.
2022-04-25 09:57:47 +02:00
if (graphics.fademode == FADE_FADING_OUT)
{
return;
}
ed.tilex = (key.mx - (key.mx % 8)) / 8;
ed.tiley = (key.my - (key.my % 8)) / 8;
if (gameScreen.scalingMode == SCALING_STRETCH) {
// In this mode specifically, we have to fix the mouse coordinates
Persist windowed mode size through fullscreen mode Previously, the game would not store the size of the window itself, and would always call SDL_GetRendererOutputSize() (via Screen::GetWindowSize()) to figure out the size of the window. The only problem is, this would return the size of the whole monitor if the game was in fullscreen mode. And the only place where the original windowed mode size was stored would be in SDL itself, but that wouldn't persist after the game was closed. So, if you exited the game while in fullscreen mode, then your window size would get set to the size of your monitor (1920 by 1080 in my case). Then when you opened the game and toggled fullscreen off, it would go back to the default window size, which is 640 by 480. This is made worse, however, if you were in forced fullscreen mode when you previously exited the game in windowed mode. In that case, the game saves the size of 1920 by 1080, but doesn't save that you were in fullscreen mode, so opening the game not in forced fullscreen mode would result in you having a 1920 by 1080 window, but in windowed mode. Meaning that not even fullscreening and unfullscreening would put the game window back to normal size. The solution, of course, is to just store the window size ourselves, like any other screen setting, and only use GetWindowSize() if needed. And just to make things clear, I've also renamed the GetWindowSize() function to GetScreenSize(), because if it was named "window" it could lead one to think that it would always return the size of the screen in windowed mode, when in fact it returns the size of the screen whatever mode it is in - fullscreen size if in fullscreen mode and window size if in windowed mode. And doing this also fixes the FIXME above Screen::isForcedFullscreen().
2023-03-21 04:59:37 +01:00
int screenwidth, screenheight;
gameScreen.GetScreenSize(&screenwidth, &screenheight);
ed.tilex = ed.tilex * 320 / screenwidth;
ed.tiley = ed.tiley * 240 / screenheight;
}
2020-01-01 21:29:24 +01:00
bool up_pressed = key.isDown(SDLK_UP) || key.isDown(SDL_CONTROLLER_BUTTON_DPAD_UP);
bool down_pressed = key.isDown(SDLK_DOWN) || key.isDown(SDL_CONTROLLER_BUTTON_DPAD_DOWN);
bool left_pressed = key.isDown(SDLK_LEFT) || key.isDown(SDL_CONTROLLER_BUTTON_DPAD_LEFT);
bool right_pressed = key.isDown(SDLK_RIGHT) || key.isDown(SDL_CONTROLLER_BUTTON_DPAD_RIGHT);
2020-01-01 21:29:24 +01:00
game.press_left = false;
game.press_right = false;
game.press_action = false;
game.press_map = false;
game.press_interact = false;
2020-01-01 21:29:24 +01:00
if (key.isDown(KEYBOARD_LEFT) || key.isDown(KEYBOARD_a) || key.controllerWantsLeft(false))
2020-01-01 21:29:24 +01:00
{
game.press_left = true;
}
if (key.isDown(KEYBOARD_RIGHT) || key.isDown(KEYBOARD_d) || key.controllerWantsRight(false))
2020-01-01 21:29:24 +01:00
{
game.press_right = true;
}
if (key.isDown(KEYBOARD_z) || key.isDown(KEYBOARD_SPACE) || key.isDown(KEYBOARD_v) || key.isDown(game.controllerButton_flip))
2020-01-01 21:29:24 +01:00
{
game.press_action = true;
};
if (key.keymap[SDLK_F9] && (ed.keydelay == 0)) {
ed.keydelay = 30;
ed.note = loc::gettext("Reloaded resources");
ed.notedelay = 45;
graphics.reloadresources();
}
2020-01-01 21:29:24 +01:00
if (key.isDown(KEYBOARD_ENTER)) game.press_map = true;
if (key.isDown(27) && !ed.settingskey)
{
ed.settingskey=true;
if (ed.textmod)
{
key.disabletextentry();
if (ed.textmod >= FIRST_ENTTEXT && ed.textmod <= LAST_ENTTEXT)
{
*ed.textptr = ed.oldenttext;
if (ed.oldenttext == "")
{
ed.removeedentity(ed.textent);
}
}
ed.textmod = TEXT_NONE;
ed.shiftmenu = false;
ed.shiftkey = false;
}
Axe manual state trackers and use SDL_IsTextInputActive() After looking at pull request #446, I got a bit annoyed that we have TWO variables, key.textentrymode and ed.textentry, that we rolled ourselves to track the state of something SDL already provides us a function to easily query: SDL_IsTextInputActive(). We don't need to have either of these two variables, and we shouldn't. So that's what I do in this patch. Both variables have been axed in favor of using this function, and I just made a wrapper out of it, named key.textentry(). For bonus points, this gets rid of the ugly NO_CUSTOM_LEVELS and NO_EDITOR ifdef in main.cpp, since text entry is enabled when entering the script list and disabled when exiting it. This makes the code there easier to read, too. Furthermore, apparently key.textentrymode was initialized to *true* instead of false... for whatever reason. But that's gone now, too. Now, you'd think there wouldn't be any downside to using SDL_IsTextInputActive(). After all, it's a function that SDL itself provides, right? Wrong. For whatever reason, it seems like text input is active *from the start of the program*, meaning that what would happen is I would go into the editor, and find that I can't move around nor place tiles nor anything else. Then I would press Esc, and then suddenly become able to do those things I wanted to do before. I have no idea why the above happens, but all I can do is to just insert an SDL_StopTextInput() immediately after the SDL_Init() in main.cpp. Of course, I have to surround it with an SDL_IsTextInputActive() check to make sure I don't do anything extraneous by stopping input when it's already stopped.
2020-08-13 08:43:25 +02:00
else if (key.textentry())
2020-01-01 21:29:24 +01:00
{
key.disabletextentry();
ed.titlemod=false;
ed.desc1mod=false;
ed.desc2mod=false;
ed.desc3mod=false;
ed.websitemod=false;
ed.creatormod=false;
music.playef(11);
2020-01-01 21:29:24 +01:00
ed.shiftmenu=false;
ed.shiftkey=false;
}
else if(ed.boundarymod>0)
{
ed.boundarymod=0;
}
else
{
bool esc_from_font = false;
music.playef(11);
if (ed.settingsmod)
{
if (ed.scripteditmod)
{
ed.scripteditmod = false;
}
else if (ed.settingsmod)
{
if (game.currentmenuname == Menu::ed_settings)
{
ed.settingsmod = false;
}
else if (game.currentmenuname == Menu::ed_font)
{
// Prevent double return
esc_from_font = true;
game.returnmenu();
map.nexttowercolour();
}
else
{
game.returnmenu();
map.nexttowercolour();
}
}
}
else
{
ed.settingsmod = true;
}
2020-01-01 21:29:24 +01:00
if (ed.settingsmod && !esc_from_font)
{
bool edsettings_in_stack = game.currentmenuname == Menu::ed_settings;
if (!edsettings_in_stack)
{
size_t i;
for (i = 0; i < game.menustack.size(); ++i)
{
if (game.menustack[i].name == Menu::ed_settings)
{
edsettings_in_stack = true;
break;
}
}
}
if (edsettings_in_stack)
{
game.returntomenu(Menu::ed_settings);
}
else
{
game.createmenu(Menu::ed_settings);
}
map.nexttowercolour();
}
2020-01-01 21:29:24 +01:00
}
}
if (!key.isDown(27))
{
ed.settingskey=false;
}
if(ed.scripteditmod)
{
if(ed.scripthelppage==0)
{
//hook select menu
if(ed.keydelay>0) ed.keydelay--;
if(up_pressed && ed.keydelay<=0)
2020-01-01 21:29:24 +01:00
{
ed.keydelay=6;
ed.hookmenu--;
}
if(down_pressed && ed.keydelay<=0)
2020-01-01 21:29:24 +01:00
{
ed.keydelay=6;
ed.hookmenu++;
}
Make `commands`, `sb`, and `hooklist` not use separate length-trackers This is a refactor that turns the script-related arrays `ed.sb`, and `ed.hooklist` into C++ vectors (`script.commands` was already a vector, it was just misused). The code handling these vectors now looks more like idiomatic C++ than sloppily-pasted pseudo-ActionScript. This removes the variables `script.scriptlength`, `ed.sblength`, and `ed.numhooks`, too. This reduces the amount of code needed to e.g. simply remove something from any of these vectors. Previously the code had to manually shift the rest of the elements down one-by-one, and doing it manually is definitely error-prone and tedious. But now we can just use fancy functions like `std::vector::erase()` and `std::remove()` to do it all in one line! Don't worry, I checked and `std::remove()` is in the C++ standard since at least 1998. This patch makes it so the `commands` vector gets cleared when `scriptclass::load()` is ran. Previously, the `commands` vector never actually properly got cleared, so there could potentially be glitches that rely on the game indexing past the bounds set by `scriptlength` but still in-bounds in the eyes of C++, and people could potentially rely on such an exploit... However, I checked, and I'm pretty sure that no such glitch previously existed at all, because the only times the vector gets indexed are when `scriptlength` is either being incremented after starting from 0 (`add()`) or when it's underneath a `position < scriptlength` conditional. Furthermore, I'm unaware of anyone who has actually found or used such an exploit, and I've been in the custom level community for 6 years. So I think it's fine.
2020-02-20 18:43:52 +01:00
if(ed.hookmenu>=(int)ed.hooklist.size())
2020-01-01 21:29:24 +01:00
{
Make `commands`, `sb`, and `hooklist` not use separate length-trackers This is a refactor that turns the script-related arrays `ed.sb`, and `ed.hooklist` into C++ vectors (`script.commands` was already a vector, it was just misused). The code handling these vectors now looks more like idiomatic C++ than sloppily-pasted pseudo-ActionScript. This removes the variables `script.scriptlength`, `ed.sblength`, and `ed.numhooks`, too. This reduces the amount of code needed to e.g. simply remove something from any of these vectors. Previously the code had to manually shift the rest of the elements down one-by-one, and doing it manually is definitely error-prone and tedious. But now we can just use fancy functions like `std::vector::erase()` and `std::remove()` to do it all in one line! Don't worry, I checked and `std::remove()` is in the C++ standard since at least 1998. This patch makes it so the `commands` vector gets cleared when `scriptclass::load()` is ran. Previously, the `commands` vector never actually properly got cleared, so there could potentially be glitches that rely on the game indexing past the bounds set by `scriptlength` but still in-bounds in the eyes of C++, and people could potentially rely on such an exploit... However, I checked, and I'm pretty sure that no such glitch previously existed at all, because the only times the vector gets indexed are when `scriptlength` is either being incremented after starting from 0 (`add()`) or when it's underneath a `position < scriptlength` conditional. Furthermore, I'm unaware of anyone who has actually found or used such an exploit, and I've been in the custom level community for 6 years. So I think it's fine.
2020-02-20 18:43:52 +01:00
ed.hookmenu=ed.hooklist.size()-1;
2020-01-01 21:29:24 +01:00
}
if(ed.hookmenu<0) ed.hookmenu=0;
if(ed.hookmenu<ed.hookmenupage)
{
ed.hookmenupage=ed.hookmenu;
}
if(ed.hookmenu>=ed.hookmenupage+9)
{
ed.hookmenupage=ed.hookmenu+8;
}
if(!key.keymap[SDLK_BACKSPACE]) ed.deletekeyheld=0;
if(key.keymap[SDLK_BACKSPACE] && ed.deletekeyheld==0 && !ed.hooklist.empty())
2020-01-01 21:29:24 +01:00
{
ed.deletekeyheld=1;
music.playef(2);
Make `commands`, `sb`, and `hooklist` not use separate length-trackers This is a refactor that turns the script-related arrays `ed.sb`, and `ed.hooklist` into C++ vectors (`script.commands` was already a vector, it was just misused). The code handling these vectors now looks more like idiomatic C++ than sloppily-pasted pseudo-ActionScript. This removes the variables `script.scriptlength`, `ed.sblength`, and `ed.numhooks`, too. This reduces the amount of code needed to e.g. simply remove something from any of these vectors. Previously the code had to manually shift the rest of the elements down one-by-one, and doing it manually is definitely error-prone and tedious. But now we can just use fancy functions like `std::vector::erase()` and `std::remove()` to do it all in one line! Don't worry, I checked and `std::remove()` is in the C++ standard since at least 1998. This patch makes it so the `commands` vector gets cleared when `scriptclass::load()` is ran. Previously, the `commands` vector never actually properly got cleared, so there could potentially be glitches that rely on the game indexing past the bounds set by `scriptlength` but still in-bounds in the eyes of C++, and people could potentially rely on such an exploit... However, I checked, and I'm pretty sure that no such glitch previously existed at all, because the only times the vector gets indexed are when `scriptlength` is either being incremented after starting from 0 (`add()`) or when it's underneath a `position < scriptlength` conditional. Furthermore, I'm unaware of anyone who has actually found or used such an exploit, and I've been in the custom level community for 6 years. So I think it's fine.
2020-02-20 18:43:52 +01:00
ed.removehook(ed.hooklist[(ed.hooklist.size()-1)-ed.hookmenu]);
2020-01-01 21:29:24 +01:00
}
if (!game.press_action && !game.press_left && !game.press_right
&& !up_pressed && !down_pressed && !key.isDown(27)) game.jumpheld = false;
2020-01-01 21:29:24 +01:00
if (!game.jumpheld)
{
if (game.press_action || game.press_left || game.press_right || game.press_map
|| up_pressed || down_pressed || key.isDown(27))
2020-01-01 21:29:24 +01:00
{
game.jumpheld = true;
}
Make `commands`, `sb`, and `hooklist` not use separate length-trackers This is a refactor that turns the script-related arrays `ed.sb`, and `ed.hooklist` into C++ vectors (`script.commands` was already a vector, it was just misused). The code handling these vectors now looks more like idiomatic C++ than sloppily-pasted pseudo-ActionScript. This removes the variables `script.scriptlength`, `ed.sblength`, and `ed.numhooks`, too. This reduces the amount of code needed to e.g. simply remove something from any of these vectors. Previously the code had to manually shift the rest of the elements down one-by-one, and doing it manually is definitely error-prone and tedious. But now we can just use fancy functions like `std::vector::erase()` and `std::remove()` to do it all in one line! Don't worry, I checked and `std::remove()` is in the C++ standard since at least 1998. This patch makes it so the `commands` vector gets cleared when `scriptclass::load()` is ran. Previously, the `commands` vector never actually properly got cleared, so there could potentially be glitches that rely on the game indexing past the bounds set by `scriptlength` but still in-bounds in the eyes of C++, and people could potentially rely on such an exploit... However, I checked, and I'm pretty sure that no such glitch previously existed at all, because the only times the vector gets indexed are when `scriptlength` is either being incremented after starting from 0 (`add()`) or when it's underneath a `position < scriptlength` conditional. Furthermore, I'm unaware of anyone who has actually found or used such an exploit, and I've been in the custom level community for 6 years. So I think it's fine.
2020-02-20 18:43:52 +01:00
if ((game.press_action || game.press_map) && !ed.hooklist.empty())
2020-01-01 21:29:24 +01:00
{
game.mapheld=true;
ed.scripthelppage=1;
key.enabletextentry();
2020-01-01 21:29:24 +01:00
key.keybuffer="";
Make `commands`, `sb`, and `hooklist` not use separate length-trackers This is a refactor that turns the script-related arrays `ed.sb`, and `ed.hooklist` into C++ vectors (`script.commands` was already a vector, it was just misused). The code handling these vectors now looks more like idiomatic C++ than sloppily-pasted pseudo-ActionScript. This removes the variables `script.scriptlength`, `ed.sblength`, and `ed.numhooks`, too. This reduces the amount of code needed to e.g. simply remove something from any of these vectors. Previously the code had to manually shift the rest of the elements down one-by-one, and doing it manually is definitely error-prone and tedious. But now we can just use fancy functions like `std::vector::erase()` and `std::remove()` to do it all in one line! Don't worry, I checked and `std::remove()` is in the C++ standard since at least 1998. This patch makes it so the `commands` vector gets cleared when `scriptclass::load()` is ran. Previously, the `commands` vector never actually properly got cleared, so there could potentially be glitches that rely on the game indexing past the bounds set by `scriptlength` but still in-bounds in the eyes of C++, and people could potentially rely on such an exploit... However, I checked, and I'm pretty sure that no such glitch previously existed at all, because the only times the vector gets indexed are when `scriptlength` is either being incremented after starting from 0 (`add()`) or when it's underneath a `position < scriptlength` conditional. Furthermore, I'm unaware of anyone who has actually found or used such an exploit, and I've been in the custom level community for 6 years. So I think it's fine.
2020-02-20 18:43:52 +01:00
ed.sbscript=ed.hooklist[(ed.hooklist.size()-1)-ed.hookmenu];
2020-01-01 21:29:24 +01:00
ed.loadhookineditor(ed.sbscript);
Make `commands`, `sb`, and `hooklist` not use separate length-trackers This is a refactor that turns the script-related arrays `ed.sb`, and `ed.hooklist` into C++ vectors (`script.commands` was already a vector, it was just misused). The code handling these vectors now looks more like idiomatic C++ than sloppily-pasted pseudo-ActionScript. This removes the variables `script.scriptlength`, `ed.sblength`, and `ed.numhooks`, too. This reduces the amount of code needed to e.g. simply remove something from any of these vectors. Previously the code had to manually shift the rest of the elements down one-by-one, and doing it manually is definitely error-prone and tedious. But now we can just use fancy functions like `std::vector::erase()` and `std::remove()` to do it all in one line! Don't worry, I checked and `std::remove()` is in the C++ standard since at least 1998. This patch makes it so the `commands` vector gets cleared when `scriptclass::load()` is ran. Previously, the `commands` vector never actually properly got cleared, so there could potentially be glitches that rely on the game indexing past the bounds set by `scriptlength` but still in-bounds in the eyes of C++, and people could potentially rely on such an exploit... However, I checked, and I'm pretty sure that no such glitch previously existed at all, because the only times the vector gets indexed are when `scriptlength` is either being incremented after starting from 0 (`add()`) or when it's underneath a `position < scriptlength` conditional. Furthermore, I'm unaware of anyone who has actually found or used such an exploit, and I've been in the custom level community for 6 years. So I think it's fine.
2020-02-20 18:43:52 +01:00
ed.sby=ed.sb.size()-1;
2020-01-01 21:29:24 +01:00
ed.pagey=0;
while(ed.sby>=ed.lines_visible-5)
2020-01-01 21:29:24 +01:00
{
ed.pagey++;
ed.sby--;
}
key.keybuffer=ed.sb[ed.pagey+ed.sby];
2023-02-23 04:13:15 +01:00
ed.sbx = UTF8_total_codepoints(ed.sb[ed.pagey+ed.sby].c_str());
music.playef(11);
2020-01-01 21:29:24 +01:00
}
}
}
else if(ed.scripthelppage==1)
{
//Script editor!
if (key.isDown(27))
{
ed.scripthelppage=0;
game.jumpheld = true;
//save the script for use again!
ed.addhook(ed.sbscript);
}
if(ed.keydelay>0) ed.keydelay--;
if(up_pressed && ed.keydelay<=0)
2020-01-01 21:29:24 +01:00
{
ed.keydelay=6;
ed.sby--;
if(ed.sby<=5)
{
if(ed.pagey>0)
{
ed.pagey--;
ed.sby++;
}
else
{
if(ed.sby<0) ed.sby=0;
}
}
key.keybuffer=ed.sb[ed.pagey+ed.sby];
}
if(down_pressed && ed.keydelay<=0)
2020-01-01 21:29:24 +01:00
{
ed.keydelay=6;
Make `commands`, `sb`, and `hooklist` not use separate length-trackers This is a refactor that turns the script-related arrays `ed.sb`, and `ed.hooklist` into C++ vectors (`script.commands` was already a vector, it was just misused). The code handling these vectors now looks more like idiomatic C++ than sloppily-pasted pseudo-ActionScript. This removes the variables `script.scriptlength`, `ed.sblength`, and `ed.numhooks`, too. This reduces the amount of code needed to e.g. simply remove something from any of these vectors. Previously the code had to manually shift the rest of the elements down one-by-one, and doing it manually is definitely error-prone and tedious. But now we can just use fancy functions like `std::vector::erase()` and `std::remove()` to do it all in one line! Don't worry, I checked and `std::remove()` is in the C++ standard since at least 1998. This patch makes it so the `commands` vector gets cleared when `scriptclass::load()` is ran. Previously, the `commands` vector never actually properly got cleared, so there could potentially be glitches that rely on the game indexing past the bounds set by `scriptlength` but still in-bounds in the eyes of C++, and people could potentially rely on such an exploit... However, I checked, and I'm pretty sure that no such glitch previously existed at all, because the only times the vector gets indexed are when `scriptlength` is either being incremented after starting from 0 (`add()`) or when it's underneath a `position < scriptlength` conditional. Furthermore, I'm unaware of anyone who has actually found or used such an exploit, and I've been in the custom level community for 6 years. So I think it's fine.
2020-02-20 18:43:52 +01:00
if(ed.sby+ed.pagey<(int)ed.sb.size()-1)
2020-01-01 21:29:24 +01:00
{
ed.sby++;
if(ed.sby>=ed.lines_visible-5)
2020-01-01 21:29:24 +01:00
{
ed.pagey++;
ed.sby--;
}
}
key.keybuffer=ed.sb[ed.pagey+ed.sby];
}
Fix frame-ordering backspacing empty line bug in script editor There is a long-standing bug with the script editor where if you delete the last character of a line, it IMMEDIATELY deletes the line you're on, and then moves your cursor back to the previous line. This is annoying, to say the least. The reason for this is that, in the sequence of events that happens in one frame (known as frame ordering), the code that backspaces one character from the line when you press Backspace is ran BEFORE the code to remove an empty line if you backspace it is ran. The former is located in key.Poll(), and the latter is located in editorinput(). Thus, when you press Backspace, the game first runs key.Poll(), sees that you've pressed Backspace, and dutifully removes the last character from a line. The line is now empty. Then, when the game gets around to the "Are you pressing Backspace on an empty line?" check in editorinput(), it thinks that you're pressing Backspace on an empty line, and then does the usual line-removing stuff. And actually, when it does the check in editorinput(), it ACTUALLY asks "Are you pressing Backspace on THIS frame and was the line empty LAST frame?" because it's checking against its own copy of the input buffer, before copying the input buffer to its own local copy. So the problem only happens if you press and hold Backspace for more than 1 frame. It's a small consolation prize for this annoyance, getting to tap-tap-tap Backspace in the hopes that you only press it for 1 frame, while in the middle of something more important to do like, oh I don't know, writing a script. So there are two potential solutions here: (1) Just change the frame ordering around. This is risky to say the least, because I'm not sure what behavior depends on exactly which frame order. It's not like it's key.Poll() and then IMMEDIATELY afterwards editorinput() is run, it's more like key.Poll(), some things that obviously depend on key.Poll() running before them, and THEN editorinput(). Also, editorinput() is only one possible thing that could be ran afterwards, on the next frame we could be running something else entirely instead. (2) Add a kludge variable to signal when the line is ALREADY empty so the game doesn't re-check the already-empty line and conclude that you're already immediately backspacing an empty line. I went with (2) for this commit, and I've added the kludge variable key.linealreadyemptykludge. However, that by itself isn't enough to fix it. It only adds about a frame or so of delay before the game goes right back to saying "Oh, you're ALREADY somehow pressing backspace again? I'll just delete this line real quick" and the behavior is basically the same as before, except now you have to hit Backspace for TWO frames or less instead of one in order to not have it happen. What we need is to have a delay set as well, when the game deletes the last line of a char. So I set ed.keydelay to 6 as well if editorinput() sses that key.linealreadyemptykludge is on.
2020-01-19 03:17:46 +01:00
if(key.linealreadyemptykludge)
{
ed.keydelay=6;
key.linealreadyemptykludge=false;
}
if(key.pressedbackspace && ed.sb[ed.pagey+ed.sby]=="" && ed.keydelay<=0)
2020-01-01 21:29:24 +01:00
{
//Remove this line completely
ed.removeline(ed.pagey+ed.sby);
ed.sby--;
if(ed.sby<=5)
{
if(ed.pagey>0)
{
ed.pagey--;
ed.sby++;
}
else
{
if(ed.sby<0) ed.sby=0;
}
}
key.keybuffer=ed.sb[ed.pagey+ed.sby];
ed.keydelay=6;
2020-01-01 21:29:24 +01:00
}
/* Remove all pipes, they are the line separator in the XML
* When this loop reaches the end, it wraps to SIZE_MAX; SIZE_MAX + 1 is 0 */
{size_t i; for (i = key.keybuffer.length() - 1; i + 1 > 0; --i)
{
if (key.keybuffer[i] == '|')
{
key.keybuffer.erase(key.keybuffer.begin() + i);
}
}}
2020-01-01 21:29:24 +01:00
ed.sb[ed.pagey+ed.sby]=key.keybuffer;
2023-02-23 04:13:15 +01:00
ed.sbx = UTF8_total_codepoints(ed.sb[ed.pagey+ed.sby].c_str());
2020-01-01 21:29:24 +01:00
if(!game.press_map && !key.isDown(27)) game.mapheld=false;
if (!game.mapheld)
{
if(game.press_map)
{
game.mapheld=true;
//Continue to next line
Make `commands`, `sb`, and `hooklist` not use separate length-trackers This is a refactor that turns the script-related arrays `ed.sb`, and `ed.hooklist` into C++ vectors (`script.commands` was already a vector, it was just misused). The code handling these vectors now looks more like idiomatic C++ than sloppily-pasted pseudo-ActionScript. This removes the variables `script.scriptlength`, `ed.sblength`, and `ed.numhooks`, too. This reduces the amount of code needed to e.g. simply remove something from any of these vectors. Previously the code had to manually shift the rest of the elements down one-by-one, and doing it manually is definitely error-prone and tedious. But now we can just use fancy functions like `std::vector::erase()` and `std::remove()` to do it all in one line! Don't worry, I checked and `std::remove()` is in the C++ standard since at least 1998. This patch makes it so the `commands` vector gets cleared when `scriptclass::load()` is ran. Previously, the `commands` vector never actually properly got cleared, so there could potentially be glitches that rely on the game indexing past the bounds set by `scriptlength` but still in-bounds in the eyes of C++, and people could potentially rely on such an exploit... However, I checked, and I'm pretty sure that no such glitch previously existed at all, because the only times the vector gets indexed are when `scriptlength` is either being incremented after starting from 0 (`add()`) or when it's underneath a `position < scriptlength` conditional. Furthermore, I'm unaware of anyone who has actually found or used such an exploit, and I've been in the custom level community for 6 years. So I think it's fine.
2020-02-20 18:43:52 +01:00
if(ed.sby+ed.pagey>=(int)ed.sb.size()) //we're on the last line
2020-01-01 21:29:24 +01:00
{
ed.sby++;
if(ed.sby>=ed.lines_visible-5)
2020-01-01 21:29:24 +01:00
{
ed.pagey++;
ed.sby--;
}
key.keybuffer=ed.sb[ed.pagey+ed.sby];
2023-02-23 04:13:15 +01:00
ed.sbx = UTF8_total_codepoints(ed.sb[ed.pagey+ed.sby].c_str());
2020-01-01 21:29:24 +01:00
}
else
{
//We're not, insert a line instead
ed.sby++;
if(ed.sby>=ed.lines_visible-5)
2020-01-01 21:29:24 +01:00
{
ed.pagey++;
ed.sby--;
}
ed.insertline(ed.sby+ed.pagey);
key.keybuffer="";
ed.sbx = 0;
}
}
}
}
}
else if (ed.textmod)
2020-01-01 21:29:24 +01:00
{
*ed.textptr = key.keybuffer;
if (!game.press_map && !key.isDown(27))
2020-01-01 21:29:24 +01:00
{
game.mapheld = false;
2020-01-01 21:29:24 +01:00
}
if (!game.mapheld && game.press_map)
2020-01-01 21:29:24 +01:00
{
game.mapheld = true;
key.disabletextentry();
switch (ed.textmod)
{
case TEXT_GOTOROOM:
{
char coord_x[16];
char coord_y[16];
const char* comma = SDL_strchr(key.keybuffer.c_str(), ',');
bool valid_input = comma != NULL;
if (valid_input)
{
SDL_strlcpy(
coord_x,
key.keybuffer.c_str(),
SDL_min((size_t) (comma - key.keybuffer.c_str() + 1), sizeof(coord_x))
);
SDL_strlcpy(coord_y, &comma[1], sizeof(coord_y));
valid_input = is_number(coord_x) && is_number(coord_y);
}
if (!valid_input)
{
ed.note = loc::gettext("ERROR: Invalid format");
ed.notedelay = 45;
break;
}
ed.levx = SDL_clamp(help.Int(coord_x) - 1, 0, cl.mapwidth - 1);
ed.levy = SDL_clamp(help.Int(coord_y) - 1, 0, cl.mapheight - 1);
graphics.backgrounddrawn = false;
break;
}
case TEXT_LOAD:
{
std::string loadstring = ed.filename + ".vvvvvv";
if (cl.load(loadstring))
{
// don't use filename, it has the full path
char buffer[3*SCREEN_WIDTH_CHARS + 1];
vformat_buf(buffer, sizeof(buffer), loc::gettext("Loaded map: {filename}.vvvvvv"), "filename:str", ed.filename.c_str());
ed.note = buffer;
}
else
{
ed.note = loc::gettext("ERROR: Could not load level");
}
ed.notedelay = 45;
break;
}
case TEXT_SAVE:
{
std::string savestring = ed.filename + ".vvvvvv";
if (cl.save(savestring))
{
char buffer[3*SCREEN_WIDTH_CHARS + 1];
vformat_buf(buffer, sizeof(buffer), loc::gettext("Saved map: {filename}.vvvvvv"), "filename:str", ed.filename.c_str());
ed.note = buffer;
}
else
{
ed.note = loc::gettext("ERROR: Could not save level!");
ed.saveandquit = false;
}
ed.notedelay = 45;
if (ed.saveandquit)
{
Enumify all fade modes This removes the magic numbers previously used for controlling the fade mode, which are really not readable at all unless you already know what they mean. 0: FADE_NONE 1: FADE_FULLY_BLACK 2: FADE_START_FADEOUT 3: FADE_FADING_OUT 4: FADE_START_FADEIN 5: FADE_FADING_IN There is also the macro FADEMODE_IS_FADING, which indicates when the intention is to only check if the game is fading right now, which wasn't clearly conveyed previously. I also took the opportunity to clean up the style of any lines I touched. This included rewriting if-else chains into case-switches, turning one-liner if-then statements into proper blocks, fixing up comments, and even commenting the `fademode == FADE_NONE` on the tower spike checks (which, it was previously undocumented why that check was there, but I think I know why it's there). As for type safety, we already get some by transforming the variable types into the enum. Assignment is prohibited without a cast. But, apparently, comparison is perfectly legal and won't even give so much as a warning. To work around this and make absolutely sure I made all existing comparisons now use the enum, I temporarily changed it to be an `enum class`, which is a C++11 feature that makes it so all comparisons are illegal. Unfortunately, it scopes them in a namespace with the same name as a class, so I had to temporarily define macros to make sure my existing code worked. I also had to temporarily up the standard in CMakeLists.txt to get it to compile. But after all that was done, I found the rest of the places where a comparison to an integer was used, and fixed them.
2022-04-25 09:57:47 +02:00
graphics.fademode = FADE_START_FADEOUT; /* quit editor */
}
break;
}
case TEXT_SCRIPT:
ed.clearscriptbuffer();
if (!ed.checkhook(key.keybuffer))
{
ed.addhook(key.keybuffer);
}
break;
default:
break;
}
ed.shiftmenu = false;
ed.shiftkey = false;
ed.textmod = TEXT_NONE;
2020-01-01 21:29:24 +01:00
}
}
Axe manual state trackers and use SDL_IsTextInputActive() After looking at pull request #446, I got a bit annoyed that we have TWO variables, key.textentrymode and ed.textentry, that we rolled ourselves to track the state of something SDL already provides us a function to easily query: SDL_IsTextInputActive(). We don't need to have either of these two variables, and we shouldn't. So that's what I do in this patch. Both variables have been axed in favor of using this function, and I just made a wrapper out of it, named key.textentry(). For bonus points, this gets rid of the ugly NO_CUSTOM_LEVELS and NO_EDITOR ifdef in main.cpp, since text entry is enabled when entering the script list and disabled when exiting it. This makes the code there easier to read, too. Furthermore, apparently key.textentrymode was initialized to *true* instead of false... for whatever reason. But that's gone now, too. Now, you'd think there wouldn't be any downside to using SDL_IsTextInputActive(). After all, it's a function that SDL itself provides, right? Wrong. For whatever reason, it seems like text input is active *from the start of the program*, meaning that what would happen is I would go into the editor, and find that I can't move around nor place tiles nor anything else. Then I would press Esc, and then suddenly become able to do those things I wanted to do before. I have no idea why the above happens, but all I can do is to just insert an SDL_StopTextInput() immediately after the SDL_Init() in main.cpp. Of course, I have to surround it with an SDL_IsTextInputActive() check to make sure I don't do anything extraneous by stopping input when it's already stopped.
2020-08-13 08:43:25 +02:00
else if (key.textentry())
{
if(ed.titlemod)
2020-01-01 21:29:24 +01:00
{
cl.title=key.keybuffer;
if (cl.title == "")
{
cl.title = "Untitled Level";
}
2020-01-01 21:29:24 +01:00
}
else if(ed.creatormod)
{
cl.creator=key.keybuffer;
if (cl.creator == "")
{
cl.creator = "Unknown";
}
2020-01-01 21:29:24 +01:00
}
else if(ed.websitemod)
{
cl.website=key.keybuffer;
2020-01-01 21:29:24 +01:00
}
else if(ed.desc1mod)
{
cl.Desc1=key.keybuffer;
2020-01-01 21:29:24 +01:00
}
else if(ed.desc2mod)
{
cl.Desc2=key.keybuffer;
2020-01-01 21:29:24 +01:00
}
else if(ed.desc3mod)
{
cl.Desc3=key.keybuffer;
2020-01-01 21:29:24 +01:00
}
if(!game.press_map && !key.isDown(27)) game.mapheld=false;
if (!game.mapheld)
{
if(game.press_map)
{
game.mapheld=true;
if(ed.titlemod)
2020-01-01 21:29:24 +01:00
{
cl.title=key.keybuffer;
if (cl.title == "")
{
cl.title = "Untitled Level";
}
2020-01-01 21:29:24 +01:00
ed.titlemod=false;
}
else if(ed.creatormod)
{
cl.creator=key.keybuffer;
if (cl.creator == "")
{
cl.creator = "Unknown";
}
2020-01-01 21:29:24 +01:00
ed.creatormod=false;
}
else if(ed.websitemod)
{
cl.website=key.keybuffer;
2020-01-01 21:29:24 +01:00
ed.websitemod=false;
}
else if(ed.desc1mod)
{
cl.Desc1=key.keybuffer;
2020-01-01 21:29:24 +01:00
}
else if(ed.desc2mod)
{
cl.Desc2=key.keybuffer;
2020-01-01 21:29:24 +01:00
}
else if(ed.desc3mod)
{
cl.Desc3=key.keybuffer;
2020-01-01 21:29:24 +01:00
ed.desc3mod=false;
}
key.disabletextentry();
if(ed.desc1mod)
{
ed.desc1mod=false;
ed.desc2mod=true;
key.enabletextentry();
key.keybuffer=cl.Desc2;
2020-01-01 21:29:24 +01:00
}
else if(ed.desc2mod)
{
ed.desc2mod=false;
if (font::height(PR_FONT_LEVEL) <= 10)
{
ed.desc3mod=true;
key.enabletextentry();
key.keybuffer=cl.Desc3;
}
else
{
cl.Desc3="";
}
2020-01-01 21:29:24 +01:00
}
music.playef(11);
2020-01-01 21:29:24 +01:00
}
}
}
else
{
if(ed.settingsmod)
{
if (!game.press_action && !game.press_left && !game.press_right
&& !up_pressed && !down_pressed) game.jumpheld = false;
2020-01-01 21:29:24 +01:00
if (!game.jumpheld)
{
if (game.press_action || game.press_left || game.press_right || game.press_map
|| up_pressed || down_pressed)
2020-01-01 21:29:24 +01:00
{
game.jumpheld = true;
}
if(game.menustart)
{
if (game.press_left || up_pressed)
2020-01-01 21:29:24 +01:00
{
game.currentmenuoption--;
}
else if (game.press_right || down_pressed)
2020-01-01 21:29:24 +01:00
{
game.currentmenuoption++;
}
}
if (game.currentmenuoption < 0) game.currentmenuoption = game.menuoptions.size()-1;
if (game.currentmenuoption >= (int) game.menuoptions.size() ) game.currentmenuoption = 0;
2020-01-01 21:29:24 +01:00
if (game.press_action)
{
editormenuactionpress();
2020-01-01 21:29:24 +01:00
}
}
}
else if (ed.keydelay > 0)
{
ed.keydelay--;
}
else if (key.keymap[SDLK_LCTRL] || key.keymap[SDLK_RCTRL])
{
// Ctrl modifiers
int texturewidth;
int textureheight;
if (cl.getroomprop(ed.levx, ed.levy)->tileset == 0)
{
if (graphics.query_texture(graphics.grphx.im_tiles, NULL, NULL, &texturewidth, &textureheight) != 0)
{
return;
}
}
else
{
if (graphics.query_texture(graphics.grphx.im_tiles2, NULL, NULL, &texturewidth, &textureheight) != 0)
{
return;
}
}
const int numtiles = (int) (texturewidth / 8) * (textureheight / 8);
ed.dmtileeditor=10;
if(left_pressed)
{
ed.dmtile--;
ed.keydelay=3;
if(ed.dmtile<0) ed.dmtile+=numtiles;
}
else if(right_pressed)
{
ed.dmtile++;
ed.keydelay=3;
if(ed.dmtile>=numtiles) ed.dmtile-=numtiles;
}
if(up_pressed)
{
ed.dmtile-=40;
ed.keydelay=3;
if(ed.dmtile<0) ed.dmtile+=numtiles;
}
else if(down_pressed)
{
ed.dmtile+=40;
ed.keydelay=3;
if(ed.dmtile>=numtiles) ed.dmtile-=numtiles;
}
}
else if (key.keymap[SDLK_LSHIFT] || key.keymap[SDLK_RSHIFT])
{
// Shift modifiers
if (key.keymap[SDLK_F1])
{
ed.switch_tileset(true);
ed.keydelay = 6;
}
if (key.keymap[SDLK_F2])
{
ed.switch_tilecol(true);
ed.keydelay = 6;
}
if (key.keymap[SDLK_F3])
{
ed.switch_enemy(true);
ed.keydelay=6;
}
if (key.keymap[SDLK_w])
{
ed.switch_warpdir(true);
ed.keydelay = 6;
}
if (up_pressed || down_pressed || left_pressed || right_pressed)
{
ed.keydelay = 6;
if (up_pressed)
{
cl.mapheight--;
}
else if (down_pressed)
{
cl.mapheight++;
}
else if (left_pressed)
{
cl.mapwidth--;
}
else if (right_pressed)
{
cl.mapwidth++;
}
cl.mapwidth = SDL_clamp(cl.mapwidth, 1, cl.maxwidth);
cl.mapheight = SDL_clamp(cl.mapheight, 1, cl.maxheight);
char buffer[3*SCREEN_WIDTH_CHARS + 1];
vformat_buf(
buffer, sizeof(buffer),
loc::gettext("Mapsize is now [{width},{height}]"),
"width:int, height:int",
cl.mapwidth, cl.mapheight
);
ed.note = buffer;
ed.notedelay = 45;
}
if (!ed.shiftkey)
{
if (ed.shiftmenu)
{
ed.shiftmenu = false;
}
else
{
ed.shiftmenu = true;
}
}
ed.shiftkey = true;
}
2020-01-01 21:29:24 +01:00
else
{
// No modifiers
ed.shiftkey=false;
if(key.keymap[SDLK_F1])
2020-01-01 21:29:24 +01:00
{
ed.switch_tileset(false);
ed.keydelay = 6;
2020-01-01 21:29:24 +01:00
}
if(key.keymap[SDLK_F2])
2020-01-01 21:29:24 +01:00
{
ed.switch_tilecol(false);
ed.keydelay = 6;
2020-01-01 21:29:24 +01:00
}
if(key.keymap[SDLK_F3])
2020-01-01 21:29:24 +01:00
{
ed.switch_enemy(false);
2020-01-01 21:29:24 +01:00
ed.keydelay=6;
}
if(key.keymap[SDLK_F4])
2020-01-01 21:29:24 +01:00
{
ed.keydelay=6;
ed.boundarytype=1;
ed.boundarymod=1;
}
if(key.keymap[SDLK_F5])
2020-01-01 21:29:24 +01:00
{
ed.keydelay=6;
ed.boundarytype=2;
ed.boundarymod=1;
}
if(key.keymap[SDLK_F10])
2020-01-01 21:29:24 +01:00
{
if(cl.getroomprop(ed.levx, ed.levy)->directmode==1)
2020-01-01 21:29:24 +01:00
{
cl.setroomdirectmode(ed.levx, ed.levy, 0);
ed.note=loc::gettext("Direct Mode Disabled");
/* Kludge fix for rainbow BG here... */
if (cl.getroomprop(ed.levx, ed.levy)->tileset == 2
&& cl.getroomprop(ed.levx, ed.levy)->tilecol == 6)
{
cl.setroomtilecol(ed.levx, ed.levy, 0);
}
2020-01-01 21:29:24 +01:00
}
else
{
cl.setroomdirectmode(ed.levx, ed.levy, 1);
ed.note=loc::gettext("Direct Mode Enabled");
2020-01-01 21:29:24 +01:00
}
graphics.backgrounddrawn = false;
2020-01-01 21:29:24 +01:00
ed.notedelay = 45;
ed.updatetiles = true;
ed.keydelay = 6;
2020-01-01 21:29:24 +01:00
}
for (int i = 0; i < NUM_EditorTools; i++)
{
if (key.keymap[ed.toolkey[i]])
{
ed.current_tool = (EditorTools) i;
}
}
2020-01-01 21:29:24 +01:00
if(key.keymap[SDLK_w])
2020-01-01 21:29:24 +01:00
{
ed.switch_warpdir(false);
ed.keydelay = 6;
2020-01-01 21:29:24 +01:00
}
if(key.keymap[SDLK_e])
2020-01-01 21:29:24 +01:00
{
ed.keydelay = 6;
ed.getlin(TEXT_ROOMNAME, loc::gettext("Enter new room name:"), const_cast<std::string*>(&(cl.getroomprop(ed.levx, ed.levy)->roomname)));
2020-01-01 21:29:24 +01:00
game.mapheld=true;
}
if (key.keymap[SDLK_g])
{
ed.keydelay = 6;
ed.getlin(TEXT_GOTOROOM, loc::gettext("Enter room coordinates x,y:"), NULL);
game.mapheld=true;
}
2020-01-01 21:29:24 +01:00
//Save and load
if(key.keymap[SDLK_s])
2020-01-01 21:29:24 +01:00
{
ed.keydelay = 6;
ed.getlin(TEXT_SAVE, loc::gettext("Enter map filename to save as:"), &(ed.filename));
2020-01-01 21:29:24 +01:00
game.mapheld=true;
}
if(key.keymap[SDLK_l])
2020-01-01 21:29:24 +01:00
{
ed.keydelay = 6;
ed.getlin(TEXT_LOAD, loc::gettext("Enter map filename to load:"), &(ed.filename));
2020-01-01 21:29:24 +01:00
game.mapheld=true;
}
if(!game.press_map) game.mapheld=false;
if (!game.mapheld)
{
if(game.press_map)
{
game.mapheld=true;
//Ok! Scan the room for the closest checkpoint
int testeditor = -1;
bool startpoint = false;
2020-01-01 21:29:24 +01:00
//First up; is there a start point on this screen?
for (size_t i = 0; i < customentities.size(); i++)
2020-01-01 21:29:24 +01:00
{
//if() on screen
if (customentities[i].t == 16 && testeditor == -1)
2020-01-01 21:29:24 +01:00
{
int tx = customentities[i].x / 40;
int ty = customentities[i].y / 30;
if (tx == ed.levx && ty == ed.levy)
2020-01-01 21:29:24 +01:00
{
testeditor = i;
startpoint = true;
2020-01-01 21:29:24 +01:00
}
}
}
if(testeditor==-1)
{
//Ok, settle for a check point
for (size_t i = 0; i < customentities.size(); i++)
2020-01-01 21:29:24 +01:00
{
//if() on screen
if (customentities[i].t == 10 && testeditor == -1)
2020-01-01 21:29:24 +01:00
{
int tx = customentities[i].x / 40;
int ty = customentities[i].y / 30;
if (tx == ed.levx && ty == ed.levy)
2020-01-01 21:29:24 +01:00
{
testeditor = i;
2020-01-01 21:29:24 +01:00
}
}
}
}
if(testeditor==-1)
{
ed.note=loc::gettext("ERROR: No checkpoint to spawn at");
2020-01-01 21:29:24 +01:00
ed.notedelay=45;
}
else
{
ed.currentghosts = 0;
if(!startpoint)
2020-01-01 21:29:24 +01:00
{
//Checkpoint spawn
int tx=customentities[testeditor].x/40;
int ty=customentities[testeditor].y/30;
game.edsavex = (customentities[testeditor].x%40)*8 - 4;
game.edsavey = (customentities[testeditor].y%30)*8;
2020-01-01 21:29:24 +01:00
game.edsaverx = 100+tx;
game.edsavery = 100+ty;
if (customentities[testeditor].p1 == 0) // NOT a bool check!
{
game.edsavegc = 1;
game.edsavey -= 2;
}
else
{
game.edsavegc = 0;
game.edsavey -= 7;
}
2020-01-01 21:29:24 +01:00
game.edsavedir = 0;
}
else
{
//Start point spawn
int tx = customentities[testeditor].x / 40;
int ty = customentities[testeditor].y / 30;
game.edsavex = (customentities[testeditor].x % 40) * 8 - 4;
game.edsavey = (customentities[testeditor].y % 30) * 8;
game.edsaverx = 100 + tx;
game.edsavery = 100 + ty;
2020-01-01 21:29:24 +01:00
game.edsavegc = 0;
game.edsavey++;
game.edsavedir = 1 - customentities[testeditor].p1;
2020-01-01 21:29:24 +01:00
}
music.haltdasmusik();
ed.returneditoralpha = 1000; // Let's start it higher than 255 since it gets clamped
ed.oldreturneditoralpha = 1000;
Refactor scriptclass::startgamemode This overhauls scriptclass::gamemode massively. The first change is that it now uses an enum, and enforces using that enum via using its type instead of an int. This is because whenever you're reading any calls to startgamemode, you have no idea what magic number actually corresponds to what unless you read startgamemode itself. And when you do read it, not every case is commented adequately, so you'd have to do more work to figure out what each case is. With the enum, it's obvious and self-evident, and that also removes the need for all the comments in the function too. Some math is still done on mode variables (to simplify time trial code), but it's okay, we can just cast between int and the enum as needed. The second is that common code is now de-duplicated. There was a lot of code that every case does, such as calling hardreset, setting Flip Mode, resetting the player, calling gotoroom and so on. Now some code may be duplicated between cases, so I've tried to group up similar cases where possible (most notable example is grouping up the main game and No Death Mode cases together). But some code still might be duplicated in the end. Which is okay - I could've tried to de-duplicate it further but that just results in logic relevant to a specific case that's located far from the actual case itself. It's much better to leave things like setting fademode or loading scripts in the case itself. This also fixes a bug since 2.3 where playing No Death Mode (and never opening and closing the options menu) and beating it would also give you the Flip Mode trophy, since turning on the flag to invalidate Flip Mode in startgamemode only happened for the main game cases and in previous versions the game relied upon this flag being set when using a teleporter for some reason (which I removed in 2.3). Now instead of specifying it per case, I just do a !map.custommode check instead so it covers every single case at once.
2022-12-29 23:01:36 +01:00
script.startgamemode(Start_EDITORPLAYTESTING);
2020-01-01 21:29:24 +01:00
}
}
}
ed.hmod = key.keymap[SDLK_h];
ed.vmod = key.keymap[SDLK_v];
ed.bmod = key.keymap[SDLK_b];
ed.cmod = key.keymap[SDLK_c];
ed.xmod = key.keymap[SDLK_x];
ed.zmod = key.keymap[SDLK_z];
2020-01-01 21:29:24 +01:00
if(key.keymap[SDLK_COMMA])
{
ed.current_tool = (EditorTools) POS_MOD(ed.current_tool - 1, NUM_EditorTools);
ed.keydelay = 6;
}
else if(key.keymap[SDLK_PERIOD])
{
ed.current_tool = (EditorTools) POS_MOD(ed.current_tool + 1, NUM_EditorTools);
ed.keydelay = 6;
}
2020-01-01 21:29:24 +01:00
if(up_pressed)
{
ed.keydelay = 6;
ed.levy--;
ed.updatetiles = true;
ed.changeroom = true;
graphics.backgrounddrawn = false;
}
else if(down_pressed)
{
ed.keydelay = 6;
ed.levy++;
ed.updatetiles = true;
ed.changeroom = true;
graphics.backgrounddrawn = false;
}
else if(left_pressed)
{
ed.keydelay = 6;
ed.levx--;
ed.updatetiles = true;
ed.changeroom = true;
graphics.backgrounddrawn = false;
}
else if(right_pressed)
{
ed.keydelay = 6;
ed.levx++;
ed.updatetiles = true;
ed.changeroom = true;
graphics.backgrounddrawn = false;
}
2020-01-01 21:29:24 +01:00
ed.levx = POS_MOD(ed.levx, cl.mapwidth);
ed.levy = POS_MOD(ed.levy, cl.mapheight);
if (key.keymap[SDLK_SPACE])
{
ed.spacemod = !ed.spacemod;
ed.keydelay = 6;
2020-01-01 21:29:24 +01:00
}
}
if(!ed.settingsmod)
{
if(ed.boundarymod>0)
{
if(key.leftbutton)
{
if(ed.lclickdelay==0)
{
if(ed.boundarymod==1)
{
ed.lclickdelay=1;
ed.boundx1=(ed.tilex*8);
ed.boundy1=(ed.tiley*8);
ed.boundarymod=2;
}
else if(ed.boundarymod==2)
{
Fix attempts to place enemy/plat bounds & scripts with bad corner order There is a certain ordering of which corners you click on to place enemy and platform boundaries, and script boxes. You must first click on the top-left corner, then click on the bottom-right corner. The visual box that is drawn after you've first clicked on the top-left corner clearly shows this intended way of doing things. However, it seems like despite the visuals, the game didn't properly prevent you from clicking on the corners in the wrong way. If you placed it from top-right to bottom-left, or bottom-left to top-right, then the game would place the boxes accordingly, and they would have a weird shape where two of its opposite sides would just be missing. But, placing them from bottom-right to top-left is prevented accordingly. The bug comes down to a simple use of "or", instead of the correct "and". This isn't the first time the wrong conjunction was used in a conditional... (8260bb2696f4843b3e6a29b0c4deea7ba6779aea, #136) Since the code block that the if-statement guards is the code that will execute if the corners placed were correct, the if-statement thus should be written in the positive case and use a more restrictive "and", instead of the negative case, which would use a more looser "or". There are less cases that are correct than cases which are incorrect - in this case, there is only 1 correct way of doing things (top-left to bottom-right), compared to 3 incorrect ways of doing things (top-right to bottom-left, bottom-left to top-right, bottom-right to top-left) - so when thinking of positive cases, you should be using "and". Or, you can always just test it. This bug has been in the game since 2.0, so it seems like no one just tested that incorrect input actually didn't work.
2021-01-09 09:27:53 +01:00
if((ed.tilex*8)+8>=ed.boundx1 && (ed.tiley*8)+8>=ed.boundy1)
2020-01-01 21:29:24 +01:00
{
ed.boundx2=(ed.tilex*8)+8;
ed.boundy2=(ed.tiley*8)+8;
}
else
{
ed.boundx2=ed.boundx1+8;
ed.boundy2=ed.boundy1+8;
}
if(ed.boundarytype==0)
{
//Script trigger
ed.lclickdelay=1;
ed.textent=customentities.size();
ed.addedentity((ed.boundx1/8)+(ed.levx*40),(ed.boundy1/8)+ (ed.levy*30),19,
2020-01-01 21:29:24 +01:00
(ed.boundx2-ed.boundx1)/8, (ed.boundy2-ed.boundy1)/8);
ed.getlin(TEXT_SCRIPT, loc::gettext("Enter script name:"), &(customentities[ed.textent].scriptname));
2020-01-01 21:29:24 +01:00
ed.lclickdelay=1;
}
else if(ed.boundarytype==1)
{
//Enemy bounds
cl.setroomenemyx1(ed.levx, ed.levy, ed.boundx1);
cl.setroomenemyy1(ed.levx, ed.levy, ed.boundy1);
cl.setroomenemyx2(ed.levx, ed.levy, ed.boundx2);
cl.setroomenemyy2(ed.levx, ed.levy, ed.boundy2);
2020-01-01 21:29:24 +01:00
}
else if(ed.boundarytype==2)
{
//Platform bounds
cl.setroomplatx1(ed.levx, ed.levy, ed.boundx1);
cl.setroomplaty1(ed.levx, ed.levy, ed.boundy1);
cl.setroomplatx2(ed.levx, ed.levy, ed.boundx2);
cl.setroomplaty2(ed.levx, ed.levy, ed.boundy2);
2020-01-01 21:29:24 +01:00
}
else if(ed.boundarytype==3)
{
//Copy
}
ed.boundarymod=0;
ed.lclickdelay=1;
}
}
}
else
{
ed.lclickdelay=0;
}
if(key.rightbutton)
{
ed.boundarymod=0;
}
}
else if(ed.warpmod)
{
//Placing warp token
if(key.leftbutton)
{
if(ed.lclickdelay==0)
{
if(ed.free(ed.tilex, ed.tiley)==0)
{
customentities[ed.warpent].p1=ed.tilex+(ed.levx*40);
customentities[ed.warpent].p2=ed.tiley+(ed.levy*30);
2020-01-01 21:29:24 +01:00
ed.warpmod=false;
ed.warpent=-1;
ed.lclickdelay=1;
}
}
}
else
{
ed.lclickdelay=0;
}
if(key.rightbutton)
{
ed.removeedentity(ed.warpent);
2020-01-01 21:29:24 +01:00
ed.warpmod=false;
ed.warpent=-1;
}
}
else
{
//Mouse input
if(key.leftbutton)
{
if (ed.lclickdelay == 0)
2020-01-01 21:29:24 +01:00
{
ed.tool_place();
2020-01-01 21:29:24 +01:00
}
}
else
{
ed.lclickdelay = 0;
2020-01-01 21:29:24 +01:00
}
if (key.rightbutton)
2020-01-01 21:29:24 +01:00
{
// place tiles
ed.tool_remove();
2020-01-01 21:29:24 +01:00
}
if(key.middlebutton)
{
ed.dmtile = cl.gettile(ed.levx, ed.levy, ed.tilex, ed.tiley);
2020-01-01 21:29:24 +01:00
}
}
}
}
if(ed.updatetiles && cl.getroomprop(ed.levx, ed.levy)->directmode==0)
2020-01-01 21:29:24 +01:00
{
ed.updatetiles=false;
//Correctly set the tiles in the current room
switch(cl.getroomprop(ed.levx, ed.levy)->tileset)
2020-01-01 21:29:24 +01:00
{
case 0: //The Space Station
for(int j=0; j<30; j++)
{
for(int i=0; i<40; i++)
{
int temp=cl.gettile(ed.levx, ed.levy, i, j);
if(temp>=3 && temp<80)
2020-01-01 21:29:24 +01:00
{
//Fix spikes
cl.settile(ed.levx, ed.levy, i, j, ed.spikedir(i, j));
2020-01-01 21:29:24 +01:00
}
else if(temp==2 || temp>=680)
2020-01-01 21:29:24 +01:00
{
//Fix background
cl.settile(
ed.levx,
ed.levy,
i,
j,
ed.backedgetile(i, j) + ed.backbase(ed.levx, ed.levy)
);
2020-01-01 21:29:24 +01:00
}
else if(temp>0)
2020-01-01 21:29:24 +01:00
{
//Fix tiles
cl.settile(
ed.levx,
ed.levy,
i,
j,
ed.edgetile(i, j) + ed.base(ed.levx, ed.levy)
);
2020-01-01 21:29:24 +01:00
}
}
}
break;
case 1: //Outside
for(int j=0; j<30; j++)
{
for(int i=0; i<40; i++)
{
int temp=cl.gettile(ed.levx, ed.levy, i, j);
if(temp>=3 && temp<80)
2020-01-01 21:29:24 +01:00
{
//Fix spikes
cl.settile(ed.levx, ed.levy, i, j, ed.spikedir(i, j));
2020-01-01 21:29:24 +01:00
}
else if(temp==2 || temp>=680)
2020-01-01 21:29:24 +01:00
{
//Fix background
cl.settile(
ed.levx,
ed.levy,
i,
j,
ed.outsideedgetile(i, j) + ed.backbase(ed.levx, ed.levy)
);
2020-01-01 21:29:24 +01:00
}
else if(temp>0)
2020-01-01 21:29:24 +01:00
{
//Fix tiles
cl.settile(
ed.levx,
ed.levy,
i,
j,
ed.edgetile(i, j) + ed.base(ed.levx, ed.levy)
);
2020-01-01 21:29:24 +01:00
}
}
}
break;
case 2: //Lab
for(int j=0; j<30; j++)
{
for(int i=0; i<40; i++)
{
int temp=cl.gettile(ed.levx, ed.levy, i, j);
if(temp>=3 && temp<80)
2020-01-01 21:29:24 +01:00
{
//Fix spikes
cl.settile(
ed.levx,
ed.levy,
i,
j,
ed.labspikedir(
i,
j,
cl.getroomprop(ed.levx, ed.levy)->tilecol
)
);
2020-01-01 21:29:24 +01:00
}
else if(temp==2 || temp>=680)
2020-01-01 21:29:24 +01:00
{
//Fix background
cl.settile(ed.levx, ed.levy, i, j, 713);
2020-01-01 21:29:24 +01:00
}
else if(temp>0)
2020-01-01 21:29:24 +01:00
{
//Fix tiles
cl.settile(
ed.levx,
ed.levy,
i,
j,
ed.edgetile(i, j) + ed.base(ed.levx, ed.levy)
);
2020-01-01 21:29:24 +01:00
}
}
}
break;
case 3: //Warp Zone/Intermission
for(int j=0; j<30; j++)
{
for(int i=0; i<40; i++)
{
int temp=cl.gettile(ed.levx, ed.levy, i, j);
if(temp>=3 && temp<80)
2020-01-01 21:29:24 +01:00
{
//Fix spikes
cl.settile(ed.levx, ed.levy, i, j, ed.spikedir(i, j));
2020-01-01 21:29:24 +01:00
}
else if(temp==2 || temp>=680)
2020-01-01 21:29:24 +01:00
{
//Fix background
cl.settile(ed.levx, ed.levy, i, j, 713);
2020-01-01 21:29:24 +01:00
}
else if(temp>0)
2020-01-01 21:29:24 +01:00
{
//Fix tiles
cl.settile(
ed.levx,
ed.levy,
i,
j,
ed.edgetile(i, j) + ed.base(ed.levx, ed.levy)
);
2020-01-01 21:29:24 +01:00
}
}
}
break;
case 4: //The ship
for(int j=0; j<30; j++)
{
for(int i=0; i<40; i++)
{
int temp=cl.gettile(ed.levx, ed.levy, i, j);
if(temp>=3 && temp<80)
2020-01-01 21:29:24 +01:00
{
//Fix spikes
cl.settile(ed.levx, ed.levy, i, j, ed.spikedir(i, j));
2020-01-01 21:29:24 +01:00
}
else if(temp==2 || temp>=680)
2020-01-01 21:29:24 +01:00
{
//Fix background
cl.settile(
ed.levx,
ed.levy,
i,
j,
ed.backedgetile(i, j) + ed.backbase(ed.levx, ed.levy)
);
2020-01-01 21:29:24 +01:00
}
else if(temp>0)
2020-01-01 21:29:24 +01:00
{
//Fix tiles
cl.settile(
ed.levx,
ed.levy,
i,
j,
ed.edgetile(i, j) + ed.base(ed.levx, ed.levy)
);
2020-01-01 21:29:24 +01:00
}
}
}
break;
case 5: //The Tower
break;
case 6: //Custom Set 1
break;
case 7: //Custom Set 2
break;
case 8: //Custom Set 3
break;
case 9: //Custom Set 4
break;
}
graphics.foregrounddrawn = false;
2020-01-01 21:29:24 +01:00
}
}
int editorclass::getenemyframe(int t)
{
switch(t)
{
case 0:
return 78;
break;
case 1:
return 88;
break;
case 2:
return 36;
break;
case 3:
return 164;
break;
case 4:
return 68;
break;
case 5:
return 48;
break;
case 6:
return 176;
break;
case 7:
return 168;
break;
case 8:
return 112;
break;
case 9:
return 114;
break;
default:
return 78;
break;
}
}
void editorclass::settile( int x, int y, int t )
{
if (x >= 0 && y >= 0 && x < 40 && y < 30)
{
cl.settile(levx, levy, x, y, t);
}
graphics.foregrounddrawn = false;
updatetiles = true;
}
int editorclass::base( int x, int y )
{
//Return the base tile for the given tileset and colour
const RoomProperty* const room = cl.getroomprop(x, y);
if(room->tileset==0) //Space Station
{
if(room->tilecol>=22)
{
return 483 + ((room->tilecol-22)*3);
}
else if(room->tilecol>=11)
{
return 283 + ((room->tilecol-11)*3);
}
else
{
return 83 + (room->tilecol*3);
}
}
else if(room->tileset==1) //Outside
{
return 480 + (room->tilecol*3);
}
else if(room->tileset==2) //Lab
{
return 280 + (room->tilecol*3);
}
else if(room->tileset==3) //Warp Zone/Intermission
{
return 80 + (room->tilecol*3);
}
else if(room->tileset==4) //SHIP
{
return 101 + (room->tilecol*3);
}
return 0;
}
int editorclass::backbase( int x, int y )
{
//Return the base tile for the background of the given tileset and colour
const RoomProperty* const room = cl.getroomprop(x, y);
if(room->tileset==0) //Space Station
{
//Pick depending on tilecol
switch(room->tilecol)
{
case 0:
case 5:
case 26:
return 680; //Blue
break;
case 3:
case 16:
case 23:
return 683; //Yellow
break;
case 9:
case 12:
case 21:
return 686; //Greeny Cyan
break;
case 4:
case 8:
case 24:
case 28:
case 30:
return 689; //Green
break;
case 20:
case 29:
return 692; //Orange
break;
case 2:
case 6:
case 11:
case 22:
case 27:
return 695; //Red
break;
case 1:
case 10:
case 15:
case 19:
case 31:
return 698; //Pink
break;
case 14:
case 18:
return 701; //Dark Blue
break;
case 7:
case 13:
case 17:
case 25:
return 704; //Cyan
break;
default:
return 680;
break;
}
}
else if(room->tileset==1) //outside
{
return 680 + (room->tilecol*3);
}
else if(room->tileset==2) //Lab
{
return 0;
}
else if(room->tileset==3) //Warp Zone/Intermission
{
return 120 + (room->tilecol*3);
}
else if(room->tileset==4) //SHIP
{
return 741 + (room->tilecol*3);
}
return 0;
}
int editorclass::at( int x, int y )
{
x = SDL_max(0, SDL_min(x, 39));
y = SDL_max(0, SDL_min(y, 29));
return cl.gettile(levx, levy, x, y);
}
int editorclass::tile_type_wrap(int x, int y)
{
x = POS_MOD(x, cl.mapwidth * 40);
y = POS_MOD(y, cl.mapheight * 30);
const RoomProperty* const room = cl.getroomprop(x / 40, y / 30);
int tile = cl.getabstile(x, y);
if (tile == 1 || (tile >= 80 && tile <= 679))
{
// It's solid.
return TileType_SOLID;
}
if ((tile >= 6 && tile <= 9) || tile == 49 || tile == 50)
{
// It's a spike!
return TileType_SPIKE;
}
if (room->tileset != 0)
{
// tiles2.png is slightly different.
if (tile >= 51 && tile <= 74)
{
// It has more spikes!
return TileType_SPIKE;
}
if (tile == 740)
{
// And a stray solid.
return TileType_SOLID;
}
}
return TileType_NONSOLID;
}
int editorclass::backonlyfree(int x, int y)
{
// Returns 1 if tile is a background tile, 0 otherwise
if (x < 0) return backonlyfree(0, y);
if (y < 0) return backonlyfree(x, 0);
if (x >= 40) return backonlyfree(39, y);
if (y >= 30) return backonlyfree(x, 29);
if (x >= 0 && y >= 0 && x < 40 && y < 30)
{
if (cl.gettile(levx, levy, x, y) >= 680)
{
return 1;
}
}
return 0;
}
int editorclass::backfree( int x, int y )
{
//Returns 0 if tile is not a block or background tile, 1 otherwise
if(x<0) return backfree(0,y);
if(y<0) return backfree(x,0);
if(x>=40) return backfree(39,y);
if(y>=30) return backfree(x,29);
if(x>=0 && y>=0 && x<40 && y<30)
{
if(cl.gettile(levx, levy, x, y)==0)
{
return 0;
}
}
return 1;
}
int editorclass::spikefree(int x, int y)
{
//Returns 0 if tile is not a block or spike, 1 otherwise
if (x == -1) return free(0, y);
if (y == -1) return free(x, 0);
if (x == 40) return free(39, y);
if (y == 30) return free(x, 29);
if (x >= 0 && y >= 0 && x < 40 && y < 30)
{
if (cl.gettile(levx, levy, x, y) == 0)
{
return 0;
}
else
{
if (cl.gettile(levx, levy, x, y) >= 680)
{
return 0;
}
}
}
return 1;
}
int editorclass::free( int x, int y )
{
//Returns 0 if tile is not a block, 1 otherwise
if(x==-1) return free(0,y);
if(y==-1) return free(x,0);
if(x==40) return free(39,y);
if(y==30) return free(x,29);
if(x>=0 && y>=0 && x<40 && y<30)
{
if(cl.gettile(levx, levy, x, y)==0)
{
return 0;
}
else
{
if(cl.gettile(levx, levy, x, y)>=2 && cl.gettile(levx, levy, x, y)<80)
{
return 0;
}
if(cl.gettile(levx, levy, x, y)>=680)
{
return 0;
}
}
}
return 1;
}
int editorclass::match( int x, int y )
{
if(free(x-1,y)==0 && free(x,y-1)==0 && free(x+1,y)==0 && free(x,y+1)==0) return 0;
if(free(x-1,y)==0 && free(x,y-1)==0) return 10;
if(free(x+1,y)==0 && free(x,y-1)==0) return 11;
if(free(x-1,y)==0 && free(x,y+1)==0) return 12;
if(free(x+1,y)==0 && free(x,y+1)==0) return 13;
if(free(x,y-1)==0) return 1;
if(free(x-1,y)==0) return 2;
if(free(x,y+1)==0) return 3;
if(free(x+1,y)==0) return 4;
if(free(x-1,y-1)==0) return 5;
if(free(x+1,y-1)==0) return 6;
if(free(x-1,y+1)==0) return 7;
if(free(x+1,y+1)==0) return 8;
return 0;
}
int editorclass::outsidematch( int x, int y )
{
if(backonlyfree(x-1,y)==0 && backonlyfree(x+1,y)==0) return 2;
if(backonlyfree(x,y-1)==0 && backonlyfree(x,y+1)==0) return 1;
return 0;
}
int editorclass::backmatch( int x, int y )
{
//Returns the first position match for a border
// 5 1 6
// 2 X 4
// 7 3 8
if(backfree(x-1,y)==0 && backfree(x,y-1)==0 && backfree(x+1,y)==0 && backfree(x,y+1)==0) return 0;
if(backfree(x-1,y)==0 && backfree(x,y-1)==0) return 10;
if(backfree(x+1,y)==0 && backfree(x,y-1)==0) return 11;
if(backfree(x-1,y)==0 && backfree(x,y+1)==0) return 12;
if(backfree(x+1,y)==0 && backfree(x,y+1)==0) return 13;
if(backfree(x,y-1)==0) return 1;
if(backfree(x-1,y)==0) return 2;
if(backfree(x,y+1)==0) return 3;
if(backfree(x+1,y)==0) return 4;
if(backfree(x-1,y-1)==0) return 5;
if(backfree(x+1,y-1)==0) return 6;
if(backfree(x-1,y+1)==0) return 7;
if(backfree(x+1,y+1)==0) return 8;
return 0;
}
int editorclass::edgetile( int x, int y )
{
switch(match(x,y))
{
case 14:
return 0;
break;
case 10:
return 80;
break;
case 11:
return 82;
break;
case 12:
return 160;
break;
case 13:
return 162;
break;
case 1:
return 81;
break;
case 2:
return 120;
break;
case 3:
return 161;
break;
case 4:
return 122;
break;
case 5:
return 42;
break;
case 6:
return 41;
break;
case 7:
return 2;
break;
case 8:
return 1;
break;
case 0:
default:
return 0;
break;
}
}
int editorclass::outsideedgetile( int x, int y )
{
switch(outsidematch(x,y))
{
case 2:
return 0;
break;
case 1:
return 1;
break;
case 0:
default:
return 2;
break;
}
}
int editorclass::backedgetile( int x, int y )
{
switch(backmatch(x,y))
{
case 14:
return 0;
break;
case 10:
return 80;
break;
case 11:
return 82;
break;
case 12:
return 160;
break;
case 13:
return 162;
break;
case 1:
return 81;
break;
case 2:
return 120;
break;
case 3:
return 161;
break;
case 4:
return 122;
break;
case 5:
return 42;
break;
case 6:
return 41;
break;
case 7:
return 2;
break;
case 8:
return 1;
break;
case 0:
default:
return 0;
break;
}
}
int editorclass::labspikedir( int x, int y, int t )
{
// a slightly more tricky case
if(free(x,y+1)==1) return 63 + (t*2);
if(free(x,y-1)==1) return 64 + (t*2);
if(free(x-1,y)==1) return 51 + (t*2);
if(free(x+1,y)==1) return 52 + (t*2);
return 63 + (t*2);
}
int editorclass::spikedir( int x, int y )
{
if(free(x,y+1)==1) return 8;
if(free(x,y-1)==1) return 9;
if(free(x-1,y)==1) return 49;
if(free(x+1,y)==1) return 50;
return 8;
}
void editorclass::switch_tileset(const bool reversed)
{
const char* tilesets[] = {"Space Station", "Outside", "Lab", "Warp Zone", "Ship"};
int tiles = cl.getroomprop(levx, levy)->tileset;
if (reversed)
{
tiles--;
}
else
{
tiles++;
}
const int modulus = SDL_arraysize(tilesets);
tiles = POS_MOD(tiles, modulus);
cl.setroomtileset(levx, levy, tiles);
clamp_tilecol(levx, levy, false);
char buffer[3*SCREEN_WIDTH_CHARS + 1];
vformat_buf(
buffer, sizeof(buffer),
loc::gettext("Now using {area} Tileset"),
"area:str",
loc::gettext(tilesets[tiles])
);
note = buffer;
notedelay = 45;
updatetiles = true;
graphics.backgrounddrawn = false;
}
void editorclass::switch_tilecol(const bool reversed)
{
int tilecol = cl.getroomprop(levx, levy)->tilecol;
if (reversed)
{
tilecol--;
}
else
{
tilecol++;
}
cl.setroomtilecol(levx, levy, tilecol);
clamp_tilecol(levx, levy, true);
notedelay = 45;
note = loc::gettext("Tileset Colour Changed");
updatetiles = true;
graphics.backgrounddrawn = false;
}
void editorclass::clamp_tilecol(const int rx, const int ry, const bool wrap)
{
const RoomProperty* const room = cl.getroomprop(rx, ry);
const int tileset = room->tileset;
int tilecol = room->tilecol;
int mincol = -1;
int maxcol = 5;
// Only Space Station allows tileset -1
if (tileset != 0)
{
mincol = 0;
}
switch (tileset)
{
case 0:
maxcol = 31;
break;
case 1:
maxcol = 7;
break;
case 2:
if (room->directmode)
{
maxcol = 6;
}
break;
case 3:
maxcol = 6;
break;
case 5:
maxcol = 29;
break;
}
// If wrap is true, wrap-around, otherwise just cap
if (tilecol > maxcol)
{
tilecol = (wrap ? mincol : maxcol);
}
if (tilecol < mincol)
{
tilecol = (wrap ? maxcol : mincol);
}
cl.setroomtilecol(rx, ry, tilecol);
}
void editorclass::switch_enemy(const bool reversed)
{
const RoomProperty* const room = cl.getroomprop(levx, levy);
int enemy = room->enemytype;
if (reversed)
{
enemy--;
}
else
{
enemy++;
}
const int modulus = 10;
enemy = POS_MOD(enemy, modulus);
cl.setroomenemytype(levx, levy, enemy);
note = loc::gettext("Enemy Type Changed");
notedelay = 45;
}
void editorclass::switch_warpdir(const bool reversed)
{
static const int modulus = 4;
const RoomProperty* const room = cl.getroomprop(levx, levy);
int warpdir = room->warpdir;
if (reversed)
{
--warpdir;
}
else
{
++warpdir;
}
warpdir = POS_MOD(warpdir, modulus);
cl.setroomwarpdir(levx, levy, warpdir);
switch (warpdir)
{
default:
note = loc::gettext("Room warping disabled");
break;
case 1:
note = loc::gettext("Room warps horizontally");
break;
case 2:
note = loc::gettext("Room warps vertically");
break;
case 3:
note = loc::gettext("Room warps in all directions");
break;
}
notedelay = 45;
graphics.backgrounddrawn = false;
}
#endif /* NO_CUSTOM_LEVELS and NO_EDITOR */