VVVVVV/desktop_version/src/Graphics.cpp

3774 lines
103 KiB
C++
Raw Normal View History

#define GRAPHICS_DEFINITION
2020-01-01 21:29:24 +01:00
#include "Graphics.h"
#include <SDL.h>
#include "Alloc.h"
#include "Constants.h"
#include "CustomLevels.h"
#include "Editor.h"
2020-01-01 21:29:24 +01:00
#include "Entity.h"
#include "Exit.h"
#include "FileSystemUtils.h"
Start rewrite of font system This is still a work in progress, but the existing font system has been removed and replaced by a new one, in Font.cpp. Design goals of the new font system include supporting colored button glyphs, different fonts for different languages, and larger fonts than 8x8 for Chinese, Japanese and Korean, while being able to support their 30000+ characters without hiccups, slowdowns or high memory usage. And to have more flexibility with fonts in general. Plus, Graphics.cpp was long enough as-is, so it's good to have a dedicated file for font storage. The old font system worked with a std::vector<SDL_Surface*> to store 8x8 surfaces for each character, and a std::map<int,int> to store mappings between codepoints and vector indexes. The new system has a per-font collection of pages for every block of 0x1000 (4096) codepoints, that may be allocated as needed. A glyph on a page contains the index of the glyph in the image (giving its coordinates), the advance (how much the cursor should advance, so the width of that glyph) and some flags which would be at least whether the glyph exists and whether it is colored. Most of the *new* features aren't implemented yet; it's currently hardcoded to the regular 8x8 font.png, but it should be functionally equivalent to the previous behavior. The only thing that doesn't really work yet is level-specific font.png, but that'll be supported again soon enough. This commit also adds fontmeta (xml) support. Since the fonts folder is mounted at graphics/, there are two main options for recognizing non-font.png fonts: the font files have to be prefixed with font (or font_) or some special file extension is involved to signal what files are fonts. I always had a font.xml in mind (so font_cn.xml, font_ja.xml, etc) but if there's ever gonna be a need for further xml files inside the graphics folder, we have a problem. So I named them .fontmeta instead. A .fontmeta file looks somewhat like this: <?xml version="1.0" encoding="UTF-8"?> <font_metadata> <width>12</width> <height>12</height> <white_teeth>1</white_teeth> <chars> <range start="0x20" end="0x7E"/> <range start="0x80" end="0x80"/> <range start="0xA0" end="0xDF"/> <range start="0x250" end="0x2A8"/> <range start="0x2AD" end="0x2AD"/> <range start="0x2C7" end="0x2C7"/> <range start="0x2C9" end="0x2CB"/> ... </chars> <special> <range start="0x00" end="0x1F" advance="6"/> <range start="0x61" end="0x66" color="1"/> <range start="0x63" end="0x63" color="0"/> </special> </font_metadata> The <chars> tag can be used to specify characters instead of in a .txt. The original idea was to just always use the existing .txt system for specifying the font charset, and only use the XML for the other stuff that the .txt doesn't cover. However, it's probably better to keep it simple if possible - having to only have a .png and a .fontmeta seems simpler than having the data spread out over three files. And a major advantage: Chinese fonts can have about 30000 characters! It's more efficient to be able to have a tag saying "now there's 20902 characters starting at U+4E00" than to include them all in a text file and having to UTF-8 decode every single one of them. If a font.txt exists, it takes priority over the <chars> tag, and in that case, there's no reason to include the <chars> tag in the XML. But font.txt has to be in the same directory as font.png, otherwise it is rejected. Same for font.fontmeta. If neither font.txt nor <chars> exist, then the font is seen as a 2.2-and-below-style ASCII font. In <special>: advance is the number of pixels the cursor advances after drawing the character (so the width of the character, without affecting the grid in the source image), color is whether the character should have its original colors retained when printed (for button glyphs). As for <white_teeth>: The renderer PR has replaced draw-time whitening of sprites/etc (using BlitSurfaceColoured) by load-time whitening of entire images (using LoadImage with TEX_WHITE as an argument). This means we have a problem: fonts have always had their glyphs whitened at printing time, and since I'm adding support for colored button glyphs, I changed it so glyphs would sometimes not be whitened. But if we can't whiten at print time, then we'd need to whiten at load time, and if we whiten the entire font, any colored glyphs will get destroyed too. If you whiten the image selectively, well, we need more code to target specific squares in the image, and it's kind of a waste when you need to whiten 30000 12x12 Chinese characters when you're only going to need a handful, but you don't know which ones. The solution: Whitening fonts is useless if all the non-colored glyphs are already white, so we don't need to do it anyway! However, any existing fonts that have non-white glyphs (and I know of at least one level like that) will still need to be whitened. So there is now a font property <white_teeth> that can be specified in the fontmeta, which indicates that the font is already pre-whitened. If not specified, traditional whitening behavior will be used, and the font cannot use colored glyphs.
2023-01-02 05:14:53 +01:00
#include "Font.h"
#include "GraphicsUtil.h"
#include "Localization.h"
2020-01-01 21:29:24 +01:00
#include "Map.h"
#include "Maths.h"
#include "Music.h"
#include "RoomnameTranslator.h"
2020-01-01 21:29:24 +01:00
#include "Screen.h"
#include "Script.h"
#include "UtilityClass.h"
#include "VFormat.h"
#include "Vlogging.h"
2020-01-01 21:29:24 +01:00
void Graphics::init(void)
2020-01-01 21:29:24 +01:00
{
flipmode = false;
setRect(tiles_rect, 0, 0, 8, 8);
setRect(sprites_rect, 0, 0, 32, 32);
2020-01-01 21:29:24 +01:00
setRect(footerrect, 0, 230, 320, 10);
setRect(tele_rect, 0, 0, 96, 96);
2020-01-01 21:29:24 +01:00
// We initialise a few things
2020-01-01 21:29:24 +01:00
linestate = 0;
2020-01-01 21:29:24 +01:00
trinketcolset = false;
showcutscenebars = false;
setbars(0);
notextoutline = false;
2020-01-01 21:29:24 +01:00
flipmode = false;
setflipmode = false;
// Initialize backgrounds
for (int i = 0; i < numstars; i++)
2020-01-01 21:29:24 +01:00
{
2023-05-22 21:26:24 +02:00
const SDL_Rect star = {(int) (fRandom() * 320), (int) (fRandom() * 240), 2, 2};
stars[i] = star;
starsspeed[i] = 4 + (fRandom() * 4);
}
2020-01-01 21:29:24 +01:00
for (int i = 0; i < numbackboxes; i++)
{
2020-01-01 21:29:24 +01:00
SDL_Rect bb;
int bvx = 0;
int bvy = 0;
if (fRandom() * 100 > 50)
2020-01-01 21:29:24 +01:00
{
bvx = 9 - (fRandom() * 19);
if (bvx > -6 && bvx < 6) bvx = 6;
bvx = bvx * 1.5;
setRect(bb, fRandom() * 320, fRandom() * 240, 32, 12);
}
else
{
bvy = 9 - (fRandom() * 19);
if (bvy > -6 && bvy < 6) bvy = 6;
bvy = bvy * 1.5;
setRect(bb, fRandom() * 320, fRandom() * 240, 12, 32);
2020-01-01 21:29:24 +01:00
}
backboxes[i] = bb;
backboxvx[i] = bvx;
backboxvy[i] = bvy;
2020-01-01 21:29:24 +01:00
}
backboxmult = 0.5 + ((fRandom() * 100) / 200);
2020-01-01 21:29:24 +01:00
backoffset = 0;
foregrounddrawn = false;
2020-01-01 21:29:24 +01:00
backgrounddrawn = false;
warpskip = 0;
spcol = 0;
spcoldel = 0;
rcol = 0;
2020-01-01 21:29:24 +01:00
crewframe = 0;
crewframedelay = 4;
menuoffset = 0;
oldmenuoffset = 0;
2020-01-01 21:29:24 +01:00
resumegamemode = false;
//Fading stuff
SDL_memset(fadebars, 0, sizeof(fadebars));
setfade(0);
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
fademode = FADE_NONE;
ingame_fademode = FADE_NONE;
2020-01-01 21:29:24 +01:00
// initialize everything else to zero
m = 0;
linedelay = 0;
gameTexture = NULL;
gameplayTexture = NULL;
menuTexture = NULL;
ghostTexture = NULL;
tempShakeTexture = NULL;
backgroundTexture = NULL;
foregroundTexture = NULL;
tempScreenshot = NULL;
tempScreenshot2x = NULL;
towerbg = TowerBG();
titlebg = TowerBG();
trinketr = 0;
trinketg = 0;
trinketb = 0;
translucentroomname = false;
alpha = 1.0f;
screenshake_x = 0;
screenshake_y = 0;
SDL_zero(col_crewred);
SDL_zero(col_crewyellow);
SDL_zero(col_crewgreen);
SDL_zero(col_crewcyan);
SDL_zero(col_crewblue);
SDL_zero(col_crewpurple);
SDL_zero(col_crewinactive);
SDL_zero(col_clock);
SDL_zero(col_trinket);
col_tr = 0;
col_tg = 0;
col_tb = 0;
kludgeswnlinewidth = false;
tiles1_mounted = false;
tiles2_mounted = false;
minimap_mounted = false;
gamecomplete_mounted = false;
levelcomplete_mounted = false;
flipgamecomplete_mounted = false;
fliplevelcomplete_mounted = false;
2020-01-01 21:29:24 +01:00
}
void Graphics::destroy(void)
{
#define CLEAR_ARRAY(name) \
for (size_t i = 0; i < name.size(); i += 1) \
{ \
VVV_freefunc(SDL_FreeSurface, name[i]); \
} \
name.clear();
CLEAR_ARRAY(sprites_surf)
CLEAR_ARRAY(flipsprites_surf)
#undef CLEAR_ARRAY
}
static SDL_Surface* tempFilterSrc = NULL;
static SDL_Surface* tempFilterDest = NULL;
void Graphics::create_buffers(void)
{
#define CREATE_TEXTURE_WITH_DIMENSIONS(w, h) \
SDL_CreateTexture( \
gameScreen.m_renderer, \
SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, \
(w), (h) \
)
#define CREATE_TEXTURE \
CREATE_TEXTURE_WITH_DIMENSIONS(SCREEN_WIDTH_PIXELS, SCREEN_HEIGHT_PIXELS)
#define CREATE_SCROLL_TEXTURE \
CREATE_TEXTURE_WITH_DIMENSIONS(SCREEN_WIDTH_PIXELS + 16, SCREEN_WIDTH_PIXELS + 16)
gameTexture = CREATE_TEXTURE;
gameplayTexture = CREATE_TEXTURE;
menuTexture = CREATE_TEXTURE;
ghostTexture = CREATE_TEXTURE;
tempShakeTexture = CREATE_TEXTURE;
foregroundTexture = CREATE_TEXTURE;
backgroundTexture = CREATE_SCROLL_TEXTURE;
tempScrollingTexture = CREATE_SCROLL_TEXTURE;
towerbg.texture = CREATE_SCROLL_TEXTURE;
titlebg.texture = CREATE_SCROLL_TEXTURE;
#undef CREATE_SCROLL_TEXTURE
#undef CREATE_TEXTURE
#undef CREATE_TEXTURE_WITH_DIMENSIONS
SDL_SetTextureScaleMode(
gameTexture,
gameScreen.isFiltered ? SDL_ScaleModeLinear : SDL_ScaleModeNearest
);
SDL_SetTextureScaleMode(
tempShakeTexture,
gameScreen.isFiltered ? SDL_ScaleModeLinear : SDL_ScaleModeNearest
);
}
void Graphics::destroy_buffers(void)
{
VVV_freefunc(SDL_DestroyTexture, gameTexture);
VVV_freefunc(SDL_DestroyTexture, gameplayTexture);
VVV_freefunc(SDL_DestroyTexture, menuTexture);
VVV_freefunc(SDL_DestroyTexture, ghostTexture);
VVV_freefunc(SDL_DestroyTexture, tempShakeTexture);
VVV_freefunc(SDL_DestroyTexture, foregroundTexture);
VVV_freefunc(SDL_DestroyTexture, backgroundTexture);
VVV_freefunc(SDL_DestroyTexture, tempScrollingTexture);
VVV_freefunc(SDL_DestroyTexture, towerbg.texture);
VVV_freefunc(SDL_DestroyTexture, titlebg.texture);
VVV_freefunc(SDL_FreeSurface, tempFilterSrc);
VVV_freefunc(SDL_FreeSurface, tempFilterDest);
VVV_freefunc(SDL_FreeSurface, tempScreenshot);
VVV_freefunc(SDL_FreeSurface, tempScreenshot2x);
}
void Graphics::drawspritesetcol(int x, int y, int t, int c)
2020-01-01 21:29:24 +01:00
{
draw_grid_tile(grphx.im_sprites, t, x, y, sprites_rect.w, sprites_rect.h, getcol(c));
2020-01-01 21:29:24 +01:00
}
void Graphics::updatetitlecolours(void)
{
col_crewred = getcol(15);
col_crewyellow = getcol(14);
col_crewgreen = getcol(13);
col_crewcyan = getcol(0);
col_crewblue = getcol(16);
col_crewpurple = getcol(20);
col_crewinactive = getcol(19);
col_clock = getcol(18);
col_trinket = getcol(18);
}
2020-01-01 21:29:24 +01:00
void Graphics::map_tab(int opt, const char* text, bool selected /*= false*/)
{
int x = opt*80 + 40;
if (selected)
{
char buffer[SCREEN_WIDTH_CHARS + 1];
vformat_buf(buffer, sizeof(buffer), loc::get_langmeta()->menu_select_tight.c_str(), "label:str", text);
font::print(PR_CEN | PR_CJK_LOW | PR_RTL_XFLIP, x, 220, buffer, 196, 196, 255 - help.glow);
}
else
{
font::print(PR_CEN | PR_CJK_LOW | PR_RTL_XFLIP, x, 220, text, 64, 64, 64);
}
}
void Graphics::map_option(int opt, int num_opts, const std::string& text, bool selected /*= false*/)
{
int x = 80 + opt*32;
int y = 136; // start from middle of menu
int yoff = -(num_opts * 12) / 2; // could be simplified to -num_opts * 6, this conveys my intent better though
yoff += opt * 12;
if (flipmode)
{
y -= yoff; // going down, which in Flip Mode means going up
y -= 40;
}
else
{
y += yoff; // going up
}
if (selected)
{
std::string text_upper(loc::toupper(text));
char buffer[SCREEN_WIDTH_CHARS + 1];
vformat_buf(buffer, sizeof(buffer), loc::get_langmeta()->menu_select.c_str(), "label:str", text_upper.c_str());
// Account for brackets
x -= (font::len(0, buffer) - font::len(0, text_upper.c_str())) / 2;
font::print(PR_RTL_XFLIP, x, y, buffer, 196, 196, 255 - help.glow);
}
else
{
font::print(PR_RTL_XFLIP, x, y, loc::remove_toupper_escape_chars(text), 96, 96, 96);
}
}
2020-01-01 21:29:24 +01:00
void Graphics::printcrewname( int x, int y, int t )
{
//Print the name of crew member t in the right colour
const uint32_t flags = flipmode ? PR_CJK_LOW : PR_CJK_HIGH;
2020-01-01 21:29:24 +01:00
switch(t)
{
case 0:
font::print(flags, x, y, loc::gettext("Viridian"), 16, 240, 240);
2020-01-01 21:29:24 +01:00
break;
case 1:
font::print(flags, x, y, loc::gettext("Violet"), 240, 16, 240);
2020-01-01 21:29:24 +01:00
break;
case 2:
font::print(flags, x, y, loc::gettext("Vitellary"), 240, 240, 16);
2020-01-01 21:29:24 +01:00
break;
case 3:
font::print(flags, x, y, loc::gettext("Vermilion"), 240, 16, 16);
2020-01-01 21:29:24 +01:00
break;
case 4:
font::print(flags, x, y, loc::gettext("Verdigris"), 16, 240, 16);
2020-01-01 21:29:24 +01:00
break;
case 5:
font::print(flags, x, y, loc::gettext("Victoria"), 16, 16, 240);
2020-01-01 21:29:24 +01:00
break;
}
}
void Graphics::printcrewnamedark( int x, int y, int t )
{
//Print the name of crew member t as above, but in black and white
const uint32_t flags = flipmode ? PR_CJK_LOW : PR_CJK_HIGH;
2020-01-01 21:29:24 +01:00
switch(t)
{
case 0:
font::print(flags, x, y, loc::gettext("Viridian"), 128,128,128);
2020-01-01 21:29:24 +01:00
break;
case 1:
font::print(flags, x, y, loc::gettext("Violet"), 128,128,128);
2020-01-01 21:29:24 +01:00
break;
case 2:
font::print(flags, x, y, loc::gettext("Vitellary"), 128,128,128);
2020-01-01 21:29:24 +01:00
break;
case 3:
font::print(flags, x, y, loc::gettext("Vermilion"), 128,128,128);
2020-01-01 21:29:24 +01:00
break;
case 4:
font::print(flags, x, y, loc::gettext("Verdigris"), 128,128,128);
2020-01-01 21:29:24 +01:00
break;
case 5:
font::print(flags, x, y, loc::gettext("Victoria"), 128,128,128);
2020-01-01 21:29:24 +01:00
break;
}
}
void Graphics::printcrewnamestatus( int x, int y, int t, bool rescued )
2020-01-01 21:29:24 +01:00
{
//Print the status of crew member t in the right colour
int r, g, b;
char gender;
2020-01-01 21:29:24 +01:00
switch(t)
{
case 0:
2023-05-22 21:06:50 +02:00
r = 12; g = 140, b = 140;
gender = 3;
2020-01-01 21:29:24 +01:00
break;
case 1:
2023-05-22 21:06:50 +02:00
r = 140; g = 12; b = 140;
gender = 2;
2020-01-01 21:29:24 +01:00
break;
case 2:
2023-05-22 21:06:50 +02:00
r = 140; g = 140; b = 12;
gender = 1;
2020-01-01 21:29:24 +01:00
break;
case 3:
2023-05-22 21:06:50 +02:00
r = 140; g = 12; b = 12;
gender = 1;
2020-01-01 21:29:24 +01:00
break;
case 4:
2023-05-22 21:06:50 +02:00
r = 12; g = 140; b = 12;
gender = 1;
2020-01-01 21:29:24 +01:00
break;
case 5:
2023-05-22 21:06:50 +02:00
r = 12; g = 12; b = 140;
gender = 2;
2020-01-01 21:29:24 +01:00
break;
default:
return;
}
const char* status_text;
if (gender == 3 && rescued)
{
status_text = loc::gettext("(that's you!)");
2020-01-01 21:29:24 +01:00
}
else if (rescued)
{
status_text = loc::gettext_case("Rescued!", gender);
}
else
{
2023-05-22 21:06:50 +02:00
r = 64; g = 64; b = 64;
status_text = loc::gettext_case("Missing...", gender);
}
font::print(flipmode ? PR_CJK_HIGH : PR_CJK_LOW, x, y, status_text, r, g, b);
2020-01-01 21:29:24 +01:00
}
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
void Graphics::print_level_creator(
uint32_t print_flags,
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
const int y,
const std::string& creator,
const uint8_t r,
const uint8_t g,
const uint8_t b
) {
/* We now display a face instead of "by {author}" for several reasons:
* - "by" may be in a different language than the author and look weird ("por various people")
* - "by" will be longer in different languages and break the limit that levels assume
* - "by" and author may need mutually incompatible fonts, e.g. Japanese level in Korean VVVVVV
* - avoids likely grammar problems: male/female difference, name inflection in user-written text...
* - it makes sense to make it a face
* - if anyone is sad about this decision, the happy face will cheer them up anyway :D */
int width_for_face = 17;
int total_width = width_for_face + font::len(print_flags, creator.c_str());
int face_x, text_x, sprite_x;
if (!font::is_rtl(print_flags))
{
face_x = (SCREEN_WIDTH_PIXELS - total_width) / 2;
text_x = face_x + width_for_face;
sprite_x = 7;
}
else
{
face_x = (SCREEN_WIDTH_PIXELS + total_width) / 2;
text_x = face_x - width_for_face;
face_x -= 10; // sprite origin
sprite_x = 103;
print_flags |= PR_RIGHT;
}
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
set_texture_color_mod(grphx.im_sprites, r, g, b);
draw_texture_part(grphx.im_sprites, face_x, y - 1, sprite_x, 2, 10, 10, 1, 1);
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
set_texture_color_mod(grphx.im_sprites, 255, 255, 255);
font::print(print_flags, text_x, y, creator, r, g, b);
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
}
int Graphics::set_render_target(SDL_Texture* texture)
2020-01-01 21:29:24 +01:00
{
const int result = SDL_SetRenderTarget(gameScreen.m_renderer, texture);
if (result != 0)
{
WHINE_ONCE_ARGS(("Could not set render target: %s", SDL_GetError()));
}
return result;
}
int Graphics::set_texture_color_mod(SDL_Texture* texture, const Uint8 r, const Uint8 g, const Uint8 b)
{
const int result = SDL_SetTextureColorMod(texture, r, g, b);
if (result != 0)
{
WHINE_ONCE_ARGS(("Could not set texture color mod: %s", SDL_GetError()));
}
return result;
2020-01-01 21:29:24 +01:00
}
int Graphics::set_texture_alpha_mod(SDL_Texture* texture, const Uint8 alpha)
{
const int result = SDL_SetTextureAlphaMod(texture, alpha);
if (result != 0)
{
WHINE_ONCE_ARGS(("Could not set texture alpha mod: %s", SDL_GetError()));
}
return result;
}
int Graphics::query_texture(SDL_Texture* texture, Uint32* format, int* access, int* w, int* h)
{
const int result = SDL_QueryTexture(texture, format, access, w, h);
if (result != 0)
{
WHINE_ONCE_ARGS(("Could not query texture: %s", SDL_GetError()));
}
return result;
}
int Graphics::set_blendmode(const SDL_BlendMode blendmode)
{
const int result = SDL_SetRenderDrawBlendMode(gameScreen.m_renderer, blendmode);
if (result != 0)
{
WHINE_ONCE_ARGS(("Could not set draw mode: %s", SDL_GetError()));
}
return result;
}
int Graphics::set_blendmode(SDL_Texture* texture, const SDL_BlendMode blendmode)
{
const int result = SDL_SetTextureBlendMode(texture, blendmode);
if (result != 0)
{
WHINE_ONCE_ARGS(("Could not set texture blend mode: %s", SDL_GetError()));
}
return result;
}
int Graphics::clear(const int r, const int g, const int b, const int a)
{
set_color(r, g, b, a);
const int result = SDL_RenderClear(gameScreen.m_renderer);
if (result != 0)
{
WHINE_ONCE_ARGS(("Could not clear current render target: %s", SDL_GetError()));
}
return result;
}
int Graphics::clear(void)
{
return clear(0, 0, 0, 255);
}
Add support for translatable sprites Language folders can now have a graphics folder, with these files: - sprites.png and flipsprites.png: spritesheets which contain translated versions of the word enemies and checkpoints - spritesmask.xml: an XML file containing all the sprites that should be copied from the translated sprites and flipsprites images to the original sprites/flipsprites. This means that the translated spritesheets don't have to contain ALL sprites - they only have to contain the translated ones. When loading them, the game assembles a combined spritesheet with translated sprites replacing English ones as needed, and this sheet is used to visually substitute the normal sprites at rendering time. It's important to note that even if 32x32 enemies have pixel-perfect hitboxes, this is only a visual change. This has been discussed several times on Discord - basically we don't want to give people unfair advantages or disadvantages because of their language setting, or change existing gameplay and speedruns tactics, which may depend on the exact pixel arrangements of the enemies. Therefore, the hitboxes are still based on the English sprites. This should be basically unnoticeable for casual players, especially with some thought from translators and artists, but there will be an option in the speedrunner menu to display the original sprites all the time. I removed the `VVV_freefunc(SDL_FreeSurface, *tilesheet)` in make_array() in Graphics.cpp, which frees grphx.im_sprites_surf and grphx.im_flipsprites_surf. Since GraphicsResources::destroy() already frees these, it looks like the only purpose the one in make_array() serves is to do it earlier. But now we need them again later (when switching languages) so let's just not free them early.
2023-09-27 03:53:36 +02:00
bool Graphics::substitute(SDL_Texture** texture)
{
/* Either keep the given texture the same and return false,
* or substitute it for a translation and return true. */
if (loc::english_sprites)
{
return false;
}
SDL_Texture* subst = NULL;
if (*texture == grphx.im_sprites)
{
subst = grphx.im_sprites_translated;
}
else if (*texture == grphx.im_flipsprites)
{
subst = grphx.im_flipsprites_translated;
}
if (subst == NULL)
{
return false;
}
// Apply the same colors as on the original
Uint8 r, g, b, a;
SDL_GetTextureColorMod(*texture, &r, &g, &b);
SDL_GetTextureAlphaMod(*texture, &a);
set_texture_color_mod(subst, r, g, b);
set_texture_alpha_mod(subst, a);
*texture = subst;
return true;
}
void Graphics::post_substitute(SDL_Texture* subst)
{
set_texture_color_mod(subst, 255, 255, 255);
set_texture_alpha_mod(subst, 255);
}
int Graphics::copy_texture(SDL_Texture* texture, const SDL_Rect* src, const SDL_Rect* dest)
{
Add support for translatable sprites Language folders can now have a graphics folder, with these files: - sprites.png and flipsprites.png: spritesheets which contain translated versions of the word enemies and checkpoints - spritesmask.xml: an XML file containing all the sprites that should be copied from the translated sprites and flipsprites images to the original sprites/flipsprites. This means that the translated spritesheets don't have to contain ALL sprites - they only have to contain the translated ones. When loading them, the game assembles a combined spritesheet with translated sprites replacing English ones as needed, and this sheet is used to visually substitute the normal sprites at rendering time. It's important to note that even if 32x32 enemies have pixel-perfect hitboxes, this is only a visual change. This has been discussed several times on Discord - basically we don't want to give people unfair advantages or disadvantages because of their language setting, or change existing gameplay and speedruns tactics, which may depend on the exact pixel arrangements of the enemies. Therefore, the hitboxes are still based on the English sprites. This should be basically unnoticeable for casual players, especially with some thought from translators and artists, but there will be an option in the speedrunner menu to display the original sprites all the time. I removed the `VVV_freefunc(SDL_FreeSurface, *tilesheet)` in make_array() in Graphics.cpp, which frees grphx.im_sprites_surf and grphx.im_flipsprites_surf. Since GraphicsResources::destroy() already frees these, it looks like the only purpose the one in make_array() serves is to do it earlier. But now we need them again later (when switching languages) so let's just not free them early.
2023-09-27 03:53:36 +02:00
bool is_substituted = substitute(&texture);
const int result = SDL_RenderCopy(gameScreen.m_renderer, texture, src, dest);
if (result != 0)
{
WHINE_ONCE_ARGS(("Could not copy texture: %s", SDL_GetError()));
}
Add support for translatable sprites Language folders can now have a graphics folder, with these files: - sprites.png and flipsprites.png: spritesheets which contain translated versions of the word enemies and checkpoints - spritesmask.xml: an XML file containing all the sprites that should be copied from the translated sprites and flipsprites images to the original sprites/flipsprites. This means that the translated spritesheets don't have to contain ALL sprites - they only have to contain the translated ones. When loading them, the game assembles a combined spritesheet with translated sprites replacing English ones as needed, and this sheet is used to visually substitute the normal sprites at rendering time. It's important to note that even if 32x32 enemies have pixel-perfect hitboxes, this is only a visual change. This has been discussed several times on Discord - basically we don't want to give people unfair advantages or disadvantages because of their language setting, or change existing gameplay and speedruns tactics, which may depend on the exact pixel arrangements of the enemies. Therefore, the hitboxes are still based on the English sprites. This should be basically unnoticeable for casual players, especially with some thought from translators and artists, but there will be an option in the speedrunner menu to display the original sprites all the time. I removed the `VVV_freefunc(SDL_FreeSurface, *tilesheet)` in make_array() in Graphics.cpp, which frees grphx.im_sprites_surf and grphx.im_flipsprites_surf. Since GraphicsResources::destroy() already frees these, it looks like the only purpose the one in make_array() serves is to do it earlier. But now we need them again later (when switching languages) so let's just not free them early.
2023-09-27 03:53:36 +02:00
if (is_substituted)
{
post_substitute(texture);
}
return result;
}
int Graphics::copy_texture(SDL_Texture* texture, const SDL_Rect* src, const SDL_Rect* dest, const double angle, const SDL_Point* center, const SDL_RendererFlip flip)
{
Add support for translatable sprites Language folders can now have a graphics folder, with these files: - sprites.png and flipsprites.png: spritesheets which contain translated versions of the word enemies and checkpoints - spritesmask.xml: an XML file containing all the sprites that should be copied from the translated sprites and flipsprites images to the original sprites/flipsprites. This means that the translated spritesheets don't have to contain ALL sprites - they only have to contain the translated ones. When loading them, the game assembles a combined spritesheet with translated sprites replacing English ones as needed, and this sheet is used to visually substitute the normal sprites at rendering time. It's important to note that even if 32x32 enemies have pixel-perfect hitboxes, this is only a visual change. This has been discussed several times on Discord - basically we don't want to give people unfair advantages or disadvantages because of their language setting, or change existing gameplay and speedruns tactics, which may depend on the exact pixel arrangements of the enemies. Therefore, the hitboxes are still based on the English sprites. This should be basically unnoticeable for casual players, especially with some thought from translators and artists, but there will be an option in the speedrunner menu to display the original sprites all the time. I removed the `VVV_freefunc(SDL_FreeSurface, *tilesheet)` in make_array() in Graphics.cpp, which frees grphx.im_sprites_surf and grphx.im_flipsprites_surf. Since GraphicsResources::destroy() already frees these, it looks like the only purpose the one in make_array() serves is to do it earlier. But now we need them again later (when switching languages) so let's just not free them early.
2023-09-27 03:53:36 +02:00
bool is_substituted = substitute(&texture);
const int result = SDL_RenderCopyEx(gameScreen.m_renderer, texture, src, dest, angle, center, flip);
if (result != 0)
{
WHINE_ONCE_ARGS(("Could not copy texture: %s", SDL_GetError()));
}
Add support for translatable sprites Language folders can now have a graphics folder, with these files: - sprites.png and flipsprites.png: spritesheets which contain translated versions of the word enemies and checkpoints - spritesmask.xml: an XML file containing all the sprites that should be copied from the translated sprites and flipsprites images to the original sprites/flipsprites. This means that the translated spritesheets don't have to contain ALL sprites - they only have to contain the translated ones. When loading them, the game assembles a combined spritesheet with translated sprites replacing English ones as needed, and this sheet is used to visually substitute the normal sprites at rendering time. It's important to note that even if 32x32 enemies have pixel-perfect hitboxes, this is only a visual change. This has been discussed several times on Discord - basically we don't want to give people unfair advantages or disadvantages because of their language setting, or change existing gameplay and speedruns tactics, which may depend on the exact pixel arrangements of the enemies. Therefore, the hitboxes are still based on the English sprites. This should be basically unnoticeable for casual players, especially with some thought from translators and artists, but there will be an option in the speedrunner menu to display the original sprites all the time. I removed the `VVV_freefunc(SDL_FreeSurface, *tilesheet)` in make_array() in Graphics.cpp, which frees grphx.im_sprites_surf and grphx.im_flipsprites_surf. Since GraphicsResources::destroy() already frees these, it looks like the only purpose the one in make_array() serves is to do it earlier. But now we need them again later (when switching languages) so let's just not free them early.
2023-09-27 03:53:36 +02:00
if (is_substituted)
{
post_substitute(texture);
}
return result;
}
int Graphics::set_color(const Uint8 r, const Uint8 g, const Uint8 b, const Uint8 a)
{
const int result = SDL_SetRenderDrawColor(gameScreen.m_renderer, r, g, b, a);
if (result != 0)
{
WHINE_ONCE_ARGS(("Could not set draw color: %s", SDL_GetError()));
}
return result;
}
int Graphics::set_color(const Uint8 r, const Uint8 g, const Uint8 b)
{
return set_color(r, g, b, 255);
}
int Graphics::set_color(const SDL_Color color)
{
return set_color(color.r, color.g, color.b, color.a);
}
int Graphics::fill_rect(const SDL_Rect* rect)
{
const int result = SDL_RenderFillRect(gameScreen.m_renderer, rect);
if (result != 0)
{
WHINE_ONCE_ARGS(("Could not draw filled rectangle: %s", SDL_GetError()));
}
return result;
}
int Graphics::fill_rect(const SDL_Rect* rect, const int r, const int g, const int b, const int a)
{
set_color(r, g, b, a);
return fill_rect(rect);
}
int Graphics::fill_rect(const SDL_Rect* rect, const int r, const int g, const int b)
{
return fill_rect(rect, r, g, b, 255);
}
int Graphics::fill_rect(const int r, const int g, const int b)
{
return fill_rect(NULL, r, g, b, 255);
}
int Graphics::fill_rect(const SDL_Rect* rect, const SDL_Color color)
{
return fill_rect(rect, color.r, color.g, color.b, color.a);
}
int Graphics::fill_rect(const int x, const int y, const int w, const int h, const int r, const int g, const int b, const int a)
{
const SDL_Rect rect = {x, y, w, h};
return fill_rect(&rect, r, g, b, a);
}
int Graphics::fill_rect(const int x, const int y, const int w, const int h, const int r, const int g, const int b)
{
return fill_rect(x, y, w, h, r, g, b, 255);
}
int Graphics::fill_rect(const SDL_Color color)
{
return fill_rect(NULL, color);
}
int Graphics::fill_rect(const int x, const int y, const int w, const int h, const SDL_Color color)
{
return fill_rect(x, y, w, h, color.r, color.g, color.b, color.a);
}
int Graphics::draw_rect(const SDL_Rect* rect)
{
const int result = SDL_RenderDrawRect(gameScreen.m_renderer, rect);
if (result != 0)
{
WHINE_ONCE_ARGS(("Could not draw rectangle: %s", SDL_GetError()));
}
return result;
}
int Graphics::draw_rect(const SDL_Rect* rect, const int r, const int g, const int b, const int a)
{
set_color(r, g, b, a);
return draw_rect(rect);
}
int Graphics::draw_rect(const SDL_Rect* rect, const int r, const int g, const int b)
{
return draw_rect(rect, r, g, b, 255);
}
int Graphics::draw_rect(const SDL_Rect* rect, const SDL_Color color)
{
return draw_rect(rect, color.r, color.g, color.b, color.a);
}
int Graphics::draw_rect(const int x, const int y, const int w, const int h, const int r, const int g, const int b, const int a)
{
const SDL_Rect rect = {x, y, w, h};
return draw_rect(&rect, r, g, b, a);
}
int Graphics::draw_rect(const int x, const int y, const int w, const int h, const int r, const int g, const int b)
{
return draw_rect(x, y, w, h, r, g, b, 255);
}
int Graphics::draw_rect(const int x, const int y, const int w, const int h, const SDL_Color color)
{
return draw_rect(x, y, w, h, color.r, color.g, color.b, color.a);
}
int Graphics::draw_line(const int x, const int y, const int x2, const int y2)
{
const int result = SDL_RenderDrawLine(gameScreen.m_renderer, x, y, x2, y2);
if (result != 0)
{
WHINE_ONCE_ARGS(("Could not draw line: %s", SDL_GetError()));
}
return result;
}
int Graphics::draw_points(const SDL_Point* points, const int count)
{
const int result = SDL_RenderDrawPoints(gameScreen.m_renderer, points, count);
if (result != 0)
{
WHINE_ONCE_ARGS(("Could not draw points: %s", SDL_GetError()));
}
return result;
}
int Graphics::draw_points(const SDL_Point* points, const int count, const int r, const int g, const int b)
{
2023-06-06 21:31:14 +02:00
set_color(r, g, b);
return draw_points(points, count);
}
void Graphics::draw_sprite(const int x, const int y, const int t, const int r, const int g, const int b)
{
draw_grid_tile(grphx.im_sprites, t, x, y, sprites_rect.w, sprites_rect.h, r, g, b);
}
void Graphics::draw_sprite(const int x, const int y, const int t, const SDL_Color color)
{
draw_grid_tile(grphx.im_sprites, t, x, y, sprites_rect.w, sprites_rect.h, color);
}
void Graphics::draw_flipsprite(const int x, const int y, const int t, const SDL_Color color)
{
draw_grid_tile(grphx.im_flipsprites, t, x, y, sprites_rect.w, sprites_rect.h, color);
}
void Graphics::scroll_texture(SDL_Texture* texture, SDL_Texture* temp, const int x, const int y)
{
SDL_Texture* target = SDL_GetRenderTarget(gameScreen.m_renderer);
SDL_Rect texture_rect = {0, 0, 0, 0};
SDL_QueryTexture(texture, NULL, NULL, &texture_rect.w, &texture_rect.h);
const SDL_Rect src = {0, 0, texture_rect.w, texture_rect.h};
const SDL_Rect dest = {x, y, texture_rect.w, texture_rect.h};
set_render_target(temp);
clear();
copy_texture(texture, &src, &dest);
set_render_target(target);
copy_texture(temp, &src, &src);
}
bool Graphics::shouldrecoloroneway(const int tilenum, const bool mounted)
{
return (tilenum >= 14 && tilenum <= 17
&& (!mounted
|| cl.onewaycol_override));
}
2023-05-22 21:06:50 +02:00
void Graphics::drawtile(int x, int y, int t)
2020-01-01 21:29:24 +01:00
{
if (shouldrecoloroneway(t, tiles1_mounted))
{
draw_grid_tile(grphx.im_tiles_tint, t, x, y, tiles_rect.w, tiles_rect.h, cl.getonewaycol());
}
else
{
draw_grid_tile(grphx.im_tiles, t, x, y, tiles_rect.w, tiles_rect.h);
}
2020-01-01 21:29:24 +01:00
}
2023-05-22 21:06:50 +02:00
void Graphics::drawtile2(int x, int y, int t)
2020-01-01 21:29:24 +01:00
{
if (shouldrecoloroneway(t, tiles2_mounted))
{
draw_grid_tile(grphx.im_tiles2_tint, t, x, y, tiles_rect.w, tiles_rect.h, cl.getonewaycol());
}
else
{
draw_grid_tile(grphx.im_tiles2, t, x, y, tiles_rect.w, tiles_rect.h);
}
2020-01-01 21:29:24 +01:00
}
2023-05-22 21:06:50 +02:00
void Graphics::drawtile3(int x, int y, int t, int off, int height_subtract /*= 0*/)
2020-01-01 21:29:24 +01:00
{
t += off * 30;
// Can't use drawgridtile because we want to draw a slice of the tile,
// so do the logic ourselves (except include height_subtract in the final call)
int width;
if (query_texture(grphx.im_tiles3, NULL, NULL, &width, NULL) != 0)
{
return;
}
const int x2 = (t % (width / 8)) * 8;
const int y2 = (t / (width / 8)) * 8;
draw_texture_part(grphx.im_tiles3, x, y, x2, y2, 8, 8 - height_subtract, 1, 1);
2020-01-01 21:29:24 +01:00
}
const char* Graphics::textbox_line(
char* buffer,
const size_t buffer_len,
const size_t textbox_i,
const size_t line_i
) {
/* Gets a line in a textbox, accounting for filling button placeholders like {b_map}.
* Takes a buffer as an argument, but DOESN'T ALWAYS write to that buffer.
* Always use the return value! ^^
* Does not check boundaries. */
const char* line = textboxes[textbox_i].lines[line_i].c_str();
if (!textboxes[textbox_i].fill_buttons)
{
return line;
}
vformat_buf(buffer, buffer_len,
line,
"b_act:but,"
"b_int:but,"
"b_map:but,"
"b_res:but,"
"b_esc:but",
vformat_button(ActionSet_InGame, Action_InGame_ACTION),
vformat_button(ActionSet_InGame, Action_InGame_Interact),
vformat_button(ActionSet_InGame, Action_InGame_Map),
vformat_button(ActionSet_InGame, Action_InGame_Restart),
vformat_button(ActionSet_InGame, Action_InGame_Esc)
);
return buffer;
}
void Graphics::drawgui(void)
2020-01-01 21:29:24 +01:00
{
int text_sign;
if (flipmode)
{
text_sign = -1;
}
else
{
text_sign = 1;
}
2020-01-01 21:29:24 +01:00
//Draw all the textboxes to the screen
2023-05-22 21:06:50 +02:00
for (size_t i = 0; i < textboxes.size(); i++)
2020-01-01 21:29:24 +01:00
{
int text_yoff;
int yp;
int font_height = font::height(textboxes[i].print_flags);
if (flipmode)
{
text_yoff = 8 + (textboxes[i].lines.size() - 1) * (font_height + textboxes[i].linegap);
}
else
{
text_yoff = 8;
}
yp = textboxes[i].yp;
if (flipmode && textboxes[i].flipme)
{
yp = SCREEN_HEIGHT_PIXELS - yp - 16 - textboxes[i].lines.size() * (font_height + textboxes[i].linegap);
}
char buffer[SCREEN_WIDTH_CHARS + 1];
int w = textboxes[i].w;
if (textboxes[i].fill_buttons)
{
/* If we can fill in buttons, the width of the box may change...
* This is Violet's fault. She decided to say a button name out loud. */
int max = 0;
for (size_t j = 0; j < textboxes[i].lines.size(); j++)
{
int len = font::len(textboxes[i].print_flags, textbox_line(buffer, sizeof(buffer), i, j));
if (len > max)
{
max = len;
}
}
w = max + 16;
}
uint32_t print_flags = textboxes[i].print_flags | PR_CJK_LOW;
int text_xp;
if (font::is_rtl(print_flags))
{
print_flags |= PR_RIGHT;
text_xp = textboxes[i].xp + w - 8;
}
else
{
text_xp = textboxes[i].xp + 8;
}
const bool transparent = (textboxes[i].r | textboxes[i].g | textboxes[i].b) == 0;
if (transparent)
{
/* To avoid the outlines for different lines overlapping the text itself,
* first draw all the outlines and then draw the text. */
size_t j;
for (j = 0; j < textboxes[i].lines.size(); j++)
2020-01-01 21:29:24 +01:00
{
font::print(
print_flags | PR_BOR,
text_xp,
yp + text_yoff + text_sign * (j * (font_height + textboxes[i].linegap)),
textbox_line(buffer, sizeof(buffer), i, j),
0, 0, 0
);
}
for (j = 0; j < textboxes[i].lines.size(); j++)
{
font::print(
print_flags,
text_xp,
yp + text_yoff + text_sign * (j * (font_height + textboxes[i].linegap)),
textbox_line(buffer, sizeof(buffer), i, j),
196, 196, 255 - help.glow
);
}
}
else
{
const float tl_lerp = lerp(textboxes[i].prev_tl, textboxes[i].tl);
const int r = textboxes[i].r * tl_lerp;
const int g = textboxes[i].g * tl_lerp;
const int b = textboxes[i].b * tl_lerp;
drawpixeltextbox(textboxes[i].xp, yp, w, textboxes[i].h, r, g, b);
2020-01-01 21:29:24 +01:00
for (size_t j = 0; j < textboxes[i].lines.size(); j++)
2020-01-01 21:29:24 +01:00
{
font::print(
print_flags | PR_BRIGHTNESS(tl_lerp*255),
text_xp,
yp + text_yoff + text_sign * (j * (font_height + textboxes[i].linegap)),
textbox_line(buffer, sizeof(buffer), i, j),
2023-01-31 02:22:43 +01:00
textboxes[i].r, textboxes[i].g, textboxes[i].b
);
2020-01-01 21:29:24 +01:00
}
}
const bool opaque = textboxes[i].tl >= 1.0;
const bool draw_overlays = opaque || transparent;
if (!draw_overlays)
{
continue;
Fix text box deltaframe flashing on deltaframes after fully opaque So #434 didn't end up solving the deltaframe flashing fully, only reduced the chances that it could happen. I've had the Level Complete image flash a few times when the Game Saved text box pops up. This seems to be because the Level Complete image is based off of the text box being at y-position 12, and the Game Saved text box is also at y-position 12. Level Complete only gets drawn if the text box additionally has a red channel value of 165, and the Game Saved text box has a red channel value of 174. However, there is a check that the text box be fully opaque first before drawing special images. So what went wrong? Well, after thinking about it for a while, I realized that even though there is indeed an opaqueness check, the alpha of the text box updates BEFORE it gets drawn. And during the deltaframes immediately after it gets updated, the text box is considered fully opaque. It's completely possible for the linear interpolation to end up with a red channel value of 165 during these deltaframes, while the text box is opaque as well. As always, it helps if you have a high refresh rate, and run the game under 40% slowdown. Anyways, so what's the final fix for this issue? Well, use the text box 'target' RGB values instead - its tr/tg/tb attributes instead of its r/g/b attributes. They are not subject to interpolation and so are completely reliable. The opaqueness check should still be kept, though, because the target values don't account for opaqueness. And this way, we get no more deltaframe flashes during text box fades. An even better fix would be to not use magic RGB values to draw special images... but that'd be something to do later.
2021-03-21 20:49:14 +01:00
}
if (textboxes[i].image == TEXTIMAGE_LEVELCOMPLETE)
{
// Level complete
const char* english = "Level Complete!";
const char* translation = loc::gettext(english);
if (SDL_strcmp(english, translation) != 0
&& !(flipmode && fliplevelcomplete_mounted)
&& !(!flipmode && levelcomplete_mounted)
)
2020-01-01 21:29:24 +01:00
{
int sc = 2;
int y = 28;
if (font::len(0, translation) > 144)
{
// We told translators how long it could be... Ah well, mitigate the damage.
sc = 1;
y += 4;
}
if (flipmode)
{
2023-05-22 21:06:50 +02:00
y = 240 - y - 8 * sc;
}
SDL_Color color = TEXT_COLOUR("cyan");
font::print((sc == 2 ? PR_2X : PR_1X) | PR_CEN, -1, y, translation, color.r, color.g, color.b);
2020-01-01 21:29:24 +01:00
}
else
{
if (flipmode)
{
drawimage(IMAGE_FLIPLEVELCOMPLETE, 0, 180, true);
}
else
{
drawimage(IMAGE_LEVELCOMPLETE, 0, 12, true);
}
}
}
else if (textboxes[i].image == TEXTIMAGE_GAMECOMPLETE)
{
// Game complete
const char* english = "Game Complete!";
const char* translation = loc::gettext(english);
if (SDL_strcmp(english, translation) != 0
&& !(flipmode && flipgamecomplete_mounted)
&& !(!flipmode && gamecomplete_mounted)
)
{
int sc = 2;
int y = 28;
if (font::len(0, translation) > 144)
{
// We told translators how long it could be... Ah well, mitigate the damage.
sc = 1;
y += 4;
}
if (flipmode)
{
2023-05-22 21:06:50 +02:00
y = 240 - y - 8 * sc;
}
font::print((sc == 2 ? PR_2X : PR_1X) | PR_CEN, -1, y, translation, 196, 196, 243);
}
else
{
if (flipmode)
{
drawimage(IMAGE_FLIPGAMECOMPLETE, 0, 180, true);
}
else
{
drawimage(IMAGE_GAMECOMPLETE, 0, 12, true);
}
}
}
for (size_t index = 0; index < textboxes[i].sprites.size(); index++)
{
TextboxSprite* sprite = &textboxes[i].sprites[index];
int y = sprite->y + yp;
if (flipmode)
{
y = yp + textboxes[i].h - sprite->y - sprites_rect.h;
}
draw_grid_tile(
grphx.im_sprites,
sprite->tile,
sprite->x + textboxes[i].xp,
y,
sprites_rect.w,
sprites_rect.h,
getcol(sprite->col),
1,
(flipmode ? -1 : 1)
);
}
2020-01-01 21:29:24 +01:00
}
}
void Graphics::updatetextboxes(void)
{
for (size_t i = 0; i < textboxes.size(); i++)
{
textboxes[i].update();
if (textboxes[i].tm == 2 && textboxes[i].tl <= 0.5)
{
textboxes.erase(textboxes.begin() + i);
i--;
continue;
}
if (textboxes[i].tl >= 1.0f
&& ((textboxes[i].r == 175 && textboxes[i].g == 175)
|| textboxes[i].r == 175
|| textboxes[i].g == 175
|| textboxes[i].b == 175)
&& (textboxes[i].r != 175 || textboxes[i].b != 175))
{
textboxes[i].rand = fRandom() * 20;
}
}
}
void Graphics::drawimagecol( int t, int xp, int yp, const SDL_Color ct, bool cent/*= false*/ )
2020-01-01 21:29:24 +01:00
{
if (!INBOUNDS_ARR(t, images) || images[t] == NULL)
{
return;
}
2020-01-01 21:29:24 +01:00
SDL_Rect trect;
trect.x = xp;
trect.y = yp;
2020-01-01 21:29:24 +01:00
if (query_texture(images[t], NULL, NULL, &trect.w, &trect.h) != 0)
2020-01-01 21:29:24 +01:00
{
return;
}
2020-01-01 21:29:24 +01:00
if (cent)
{
trect.x = (int) ((SCREEN_WIDTH_PIXELS - trect.w) / 2);
2020-01-01 21:29:24 +01:00
}
set_texture_color_mod(images[t], ct.r, ct.g, ct.b);
draw_texture(images[t], trect.x, trect.y);
set_texture_color_mod(images[t], 255, 255, 255);
2020-01-01 21:29:24 +01:00
}
void Graphics::drawimage( int t, int xp, int yp, bool cent/*=false*/ )
{
if (!INBOUNDS_ARR(t, images) || images[t] == NULL)
{
return;
}
2020-01-01 21:29:24 +01:00
SDL_Rect trect;
trect.x = xp;
trect.y = yp;
if (query_texture(images[t], NULL, NULL, &trect.w, &trect.h) != 0)
{
return;
}
2020-01-01 21:29:24 +01:00
if (cent)
{
trect.x = (int) ((SCREEN_WIDTH_PIXELS - trect.w) / 2);
2020-01-01 21:29:24 +01:00
}
draw_texture(images[t], trect.x, trect.y);
}
void Graphics::drawpartimage(const int t, const int xp, const int yp, const int wp, const int hp)
{
if (!INBOUNDS_ARR(t, images) || images[t] == NULL)
2020-01-01 21:29:24 +01:00
{
return;
}
draw_texture_part(images[t], xp, yp, 0, 0, wp, hp, 1, 1);
}
2020-01-01 21:29:24 +01:00
void Graphics::draw_texture(SDL_Texture* image, const int x, const int y)
{
int w, h;
2020-01-01 21:29:24 +01:00
if (query_texture(image, NULL, NULL, &w, &h) != 0)
{
return;
2020-01-01 21:29:24 +01:00
}
const SDL_Rect dstrect = {x, y, w, h};
copy_texture(image, NULL, &dstrect);
2020-01-01 21:29:24 +01:00
}
void Graphics::draw_texture_part(SDL_Texture* image, const int x, const int y, const int x2, const int y2, const int w, const int h, const int scalex, const int scaley)
2020-01-01 21:29:24 +01:00
{
const SDL_Rect srcrect = {x2, y2, w, h};
int flip = SDL_FLIP_NONE;
2020-01-01 21:29:24 +01:00
if (scalex < 0)
{
flip |= SDL_FLIP_HORIZONTAL;
}
if (scaley < 0)
{
flip |= SDL_FLIP_VERTICAL;
}
const SDL_Rect dstrect = {x, y, w * SDL_abs(scalex), h * SDL_abs(scaley)};
copy_texture(image, &srcrect, &dstrect, 0, NULL, (SDL_RendererFlip) flip);
}
2020-01-01 21:29:24 +01:00
void Graphics::draw_grid_tile(SDL_Texture* texture, const int t, const int x, const int y, const int width, const int height, const int scalex, const int scaley)
{
int tex_width;
2020-01-01 21:29:24 +01:00
if (query_texture(texture, NULL, NULL, &tex_width, NULL) != 0)
{
return;
}
2020-01-01 21:29:24 +01:00
const int x2 = (t % (tex_width / width)) * width;
const int y2 = (t / (tex_width / width)) * height;
draw_texture_part(texture, x, y, x2, y2, width, height, scalex, scaley);
}
void Graphics::draw_grid_tile(
SDL_Texture* texture, const int t,
const int x, const int y, const int width, const int height
) {
draw_grid_tile(texture, t, x, y, width, height, 1, 1);
}
void Graphics::draw_grid_tile(
SDL_Texture* texture, const int t,
const int x, const int y, const int width, const int height,
const int r, const int g, const int b, const int a,
const int scalex, const int scaley
) {
set_texture_color_mod(texture, r, g, b);
set_texture_alpha_mod(texture, a);
draw_grid_tile(texture, t, x, y, width, height, scalex, scaley);
set_texture_color_mod(texture, 255, 255, 255);
set_texture_alpha_mod(texture, 255);
}
void Graphics::draw_grid_tile(
SDL_Texture* texture, const int t,
const int x, const int y, const int width, const int height,
const int r, const int g, const int b, const int a
) {
draw_grid_tile(texture, t, x, y, width, height, r, g, b, a, 1, 1);
}
void Graphics::draw_grid_tile(
SDL_Texture* texture, const int t,
const int x, const int y, const int width, const int height,
const int r, const int g, const int b,
const int scalex, const int scaley
) {
draw_grid_tile(texture, t, x, y, width, height, r, g, b, 255, scalex, scaley);
}
void Graphics::draw_grid_tile(
SDL_Texture* texture, const int t,
const int x, const int y, const int width, const int height,
const int r, const int g, const int b
) {
draw_grid_tile(texture, t, x, y, width, height, r, g, b, 255);
}
void Graphics::draw_grid_tile(
SDL_Texture* texture, const int t,
const int x, const int y, const int width, const int height,
const SDL_Color color,
const int scalex, const int scaley
) {
draw_grid_tile(texture, t, x, y, width, height, color.r, color.g, color.b, color.a, scalex, scaley);
}
void Graphics::draw_grid_tile(
SDL_Texture* texture, const int t,
const int x, const int y, const int width, const int height,
const SDL_Color color
) {
draw_grid_tile(texture, t, x, y, width, height, color, 1, 1);
2020-01-01 21:29:24 +01:00
}
void Graphics::cutscenebars(void)
2020-01-01 21:29:24 +01:00
{
const int usethispos = lerp(oldcutscenebarspos, cutscenebarspos);
2020-01-01 21:29:24 +01:00
if (showcutscenebars)
{
fill_rect(0, 0, usethispos, 16, 0, 0, 0);
2023-05-22 21:06:50 +02:00
fill_rect(360 - usethispos, 224, usethispos, 16, 0, 0, 0);
2020-01-01 21:29:24 +01:00
}
else if (cutscenebarspos > 0) //disappearing
2020-01-01 21:29:24 +01:00
{
//draw
fill_rect(0, 0, usethispos, 16, 0, 0, 0);
2023-05-22 21:06:50 +02:00
fill_rect(360 - usethispos, 224, usethispos, 16, 0, 0, 0);
2020-01-01 21:29:24 +01:00
}
}
void Graphics::cutscenebarstimer(void)
{
oldcutscenebarspos = cutscenebarspos;
if (showcutscenebars)
{
cutscenebarspos += 25;
cutscenebarspos = SDL_min(cutscenebarspos, 361);
}
else if (cutscenebarspos > 0)
{
//disappearing
cutscenebarspos -= 25;
cutscenebarspos = SDL_max(cutscenebarspos, 0);
}
}
void Graphics::setbars(const int position)
{
cutscenebarspos = position;
oldcutscenebarspos = position;
}
2023-05-22 21:06:50 +02:00
void Graphics::drawcrewman(int x, int y, int t, bool act, bool noshift /*=false*/)
2020-01-01 21:29:24 +01:00
{
if (!act)
{
if (noshift)
{
if (flipmode)
{
draw_sprite(x, y, 14, col_crewinactive);
2020-01-01 21:29:24 +01:00
}
else
{
draw_sprite(x, y, 12, col_crewinactive);
2020-01-01 21:29:24 +01:00
}
}
else
{
if (flipmode)
{
draw_sprite(x - 8, y, 14, col_crewinactive);
2020-01-01 21:29:24 +01:00
}
else
{
draw_sprite(x - 8, y, 12, col_crewinactive);
2020-01-01 21:29:24 +01:00
}
}
}
else
{
if (flipmode) crewframe += 6;
switch(t)
{
case 0:
draw_sprite(x, y, crewframe, col_crewcyan);
2020-01-01 21:29:24 +01:00
break;
case 1:
draw_sprite(x, y, crewframe, col_crewpurple);
2020-01-01 21:29:24 +01:00
break;
case 2:
draw_sprite(x, y, crewframe, col_crewyellow);
2020-01-01 21:29:24 +01:00
break;
case 3:
draw_sprite(x, y, crewframe, col_crewred);
2020-01-01 21:29:24 +01:00
break;
case 4:
draw_sprite(x, y, crewframe, col_crewgreen);
2020-01-01 21:29:24 +01:00
break;
case 5:
draw_sprite(x, y, crewframe, col_crewblue);
2020-01-01 21:29:24 +01:00
break;
}
if (flipmode) crewframe -= 6;
}
}
void Graphics::drawpixeltextbox(
const int x,
const int y,
const int w,
const int h,
const int r,
const int g,
const int b
) {
int k;
2020-01-01 21:29:24 +01:00
2023-05-22 21:06:50 +02:00
fill_rect(x, y, w, h, r / 6, g / 6, b / 6);
2020-01-01 21:29:24 +01:00
/* Horizontal tiles */
2023-05-22 21:06:50 +02:00
for (k = 0; k < w / 8 - 2; ++k)
2020-01-01 21:29:24 +01:00
{
2023-05-22 21:06:50 +02:00
drawcoloredtile(x + 8 + k * 8, y, 41, r, g, b);
drawcoloredtile(x + 8 + k * 8, y + h - 8, 46, r, g, b);
2020-01-01 21:29:24 +01:00
}
if (w % 8 != 0)
{
/* Fill in horizontal gap */
drawcoloredtile(x + w - 16, y, 41, r, g, b);
drawcoloredtile(x + w - 16, y + h - 8, 46, r, g, b);
}
/* Vertical tiles */
2023-05-22 21:06:50 +02:00
for (k = 0; k < h / 8 - 2; ++k)
2020-01-01 21:29:24 +01:00
{
2023-05-22 21:06:50 +02:00
drawcoloredtile(x, y + 8 + k * 8, 43, r, g, b);
drawcoloredtile(x + w - 8, y + 8 + k * 8, 44, r, g, b);
2020-01-01 21:29:24 +01:00
}
if (h % 8 != 0)
{
/* Fill in vertical gap */
drawcoloredtile(x, y + h - 16, 43, r, g, b);
drawcoloredtile(x + w - 8, y + h - 16, 44, r, g, b);
}
/* Corners */
2020-01-01 21:29:24 +01:00
drawcoloredtile(x, y, 40, r, g, b);
drawcoloredtile(x + w - 8, y, 42, r, g, b);
drawcoloredtile(x, y + h - 8, 45, r, g, b);
drawcoloredtile(x + w - 8, y + h - 8, 47, r, g, b);
2020-01-01 21:29:24 +01:00
}
void Graphics::textboxactive(void)
2020-01-01 21:29:24 +01:00
{
//Remove all but the most recent textbox
for (int i = 0; i < (int) textboxes.size(); i++)
2020-01-01 21:29:24 +01:00
{
if (m != i) textboxes[i].remove();
2020-01-01 21:29:24 +01:00
}
}
void Graphics::textboxremovefast(void)
2020-01-01 21:29:24 +01:00
{
//Remove all textboxes
for (size_t i = 0; i < textboxes.size(); i++)
2020-01-01 21:29:24 +01:00
{
textboxes[i].removefast();
2020-01-01 21:29:24 +01:00
}
}
void Graphics::textboxremove(void)
2020-01-01 21:29:24 +01:00
{
//Remove all textboxes
for (size_t i = 0; i < textboxes.size(); i++)
2020-01-01 21:29:24 +01:00
{
textboxes[i].remove();
2020-01-01 21:29:24 +01:00
}
}
2023-05-22 21:06:50 +02:00
void Graphics::textboxtimer(int t)
2020-01-01 21:29:24 +01:00
{
if (!INBOUNDS_VEC(m, textboxes))
{
vlog_error("textboxtimer() out-of-bounds!");
return;
}
2023-05-22 21:06:50 +02:00
textboxes[m].timer = t;
2020-01-01 21:29:24 +01:00
}
void Graphics::addsprite(int x, int y, int tile, int col)
{
if (!INBOUNDS_VEC(m, textboxes))
{
vlog_error("addsprite() out-of-bounds!");
return;
}
textboxes[m].addsprite(x, y, tile, col);
}
void Graphics::setimage(TextboxImage image)
{
if (!INBOUNDS_VEC(m, textboxes))
{
vlog_error("setimage() out-of-bounds!");
return;
}
textboxes[m].setimage(image);
}
void Graphics::addline( const std::string& t )
2020-01-01 21:29:24 +01:00
{
if (!INBOUNDS_VEC(m, textboxes))
{
vlog_error("addline() out-of-bounds!");
return;
}
textboxes[m].addline(t);
2020-01-01 21:29:24 +01:00
}
void Graphics::setlarge(bool large)
{
if (!INBOUNDS_VEC(m, textboxes))
{
vlog_error("setlarge() out-of-bounds!");
return;
}
textboxes[m].large = large;
}
Save special text box state using functions This adds a way to save the text box state of the crew remaining, ACTION prompt, etc. text boxes by just letting there be a function that is called to retranslate the text box when needed. It also adds a way to ignore translating a text box and to leave it alone, in case there's actually no text in the text box, which is the case with Level Complete and Game Complete. Both ways are now in an enum, TextboxTranslate. The former is TEXTTRANSLATE_FUNCTION and the latter is TEXTTRANSLATE_NONE. The existing way of translating text boxes became TEXTTRANSLATE_CUTSCENE, since it's only used for cutscene scripts. Here's a quick guide to the three ways of creating a text box now. - TEXTTRANSLATE_NONE: You must call graphics.textboxoriginalcontextauto() to save the existing text to the original context of the text box, as that will be copied back to the text box after the text of the text box is updated due to not having a translation. - TEXTTRANSLATE_CUTSCENE: Translates the text from cutscenes.xml, and overrides the spacing (padding and text centering). Shouldn't need to be used outside of scriptclass. - TEXTTRANSLATE_FUNCTION: You must pass in a function that takes in a single parameter, a pointer to the textboxclass object to be modified. General advice when retranslating text is to clear the `lines` vector and then push_back the retranslated text. The function is also solely responsible for spacing. In most cases, you will also need to call graphics.textboxapplyposition() or graphics.textboxadjust() afterwards. (Some text boxes shouldn't use graphics.textboxadjust() as they are within the 10-pixel inner border around the screen that textboxclass::adjust tries to push the text box out of.) This commit doesn't fix every text box just yet, though. But it fixes the Level Complete, Game Complete, crew remaining, and ACTION prompt text boxes, for a start.
2024-01-21 05:27:31 +01:00
void Graphics::textboxapplyposition(void)
{
if (!INBOUNDS_VEC(m, textboxes))
{
vlog_error("textboxapplyposition() out-of-bounds!");
return;
}
textboxes[m].applyposition();
}
void Graphics::setlinegap(int customvalue)
{
if (!INBOUNDS_VEC(m, textboxes))
{
vlog_error("setlinegap() out-of-bounds!");
return;
}
textboxes[m].linegap = customvalue;
textboxes[m].resize();
}
int Graphics::getlinegap(void)
{
// Don't use linegaps in custom levels, for now anyway
if (map.custommode) return 0;
return 1;
}
2020-01-01 21:29:24 +01:00
void Graphics::textboxindex(const int index)
{
if (!INBOUNDS_VEC(m, textboxes))
{
vlog_error("textboxindex() out-of-bounds!");
return;
}
textboxes[m].other_textbox_index = index;
}
void Graphics::createtextboxreal(
const std::string& t,
int xp, int yp,
int r, int g, int b,
bool flipme
) {
m = textboxes.size();
2020-01-01 21:29:24 +01:00
2023-05-22 21:06:50 +02:00
if (m < 20)
2020-01-01 21:29:24 +01:00
{
textboxclass text(getlinegap());
text.lines.push_back(t);
text.xp = xp;
if (xp == -1) text.xp = 160 - ((font::len(PR_FONT_LEVEL, t.c_str()) / 2) + 8);
text.yp = yp;
text.initcol(r, g, b);
text.flipme = flipme;
text.resize();
textboxes.push_back(text);
2020-01-01 21:29:24 +01:00
}
}
void Graphics::createtextbox(
const std::string& t,
int xp, int yp,
SDL_Color color
) {
createtextboxreal(t, xp, yp, color.r, color.g, color.b, false);
}
void Graphics::createtextbox(
const std::string& t,
int xp, int yp,
int r, int g, int b
) {
createtextboxreal(t, xp, yp, r, g, b, false);
}
void Graphics::createtextboxflipme(
const std::string& t,
int xp, int yp,
SDL_Color color
) {
createtextboxreal(t, xp, yp, color.r, color.g, color.b, true);
}
void Graphics::createtextboxflipme(
const std::string& t,
int xp, int yp,
int r, int g, int b
) {
createtextboxreal(t, xp, yp, r, g, b, true);
}
void Graphics::drawfade(void)
2020-01-01 21:29:24 +01:00
{
const int usethisamount = lerp(oldfadeamount, fadeamount);
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
switch (fademode)
2020-01-01 21:29:24 +01:00
{
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
case FADE_FULLY_BLACK:
case FADE_START_FADEIN:
fill_rect(0, 0, 0);
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
break;
case FADE_FADING_OUT:
for (size_t i = 0; i < SDL_arraysize(fadebars); i++)
2020-01-01 21:29:24 +01:00
{
fill_rect(fadebars[i], i * 16, usethisamount, 16, 0, 0, 0);
2020-01-01 21:29:24 +01:00
}
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
break;
case FADE_FADING_IN:
for (size_t i = 0; i < SDL_arraysize(fadebars); i++)
2020-01-01 21:29:24 +01:00
{
2023-05-22 21:06:50 +02:00
fill_rect(fadebars[i] - usethisamount, i * 16, 500, 16, 0, 0, 0);
2020-01-01 21:29:24 +01:00
}
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
break;
case FADE_NONE:
case FADE_START_FADEOUT:
break;
2020-01-01 21:29:24 +01:00
}
}
void Graphics::processfade(void)
2020-01-01 21:29:24 +01:00
{
oldfadeamount = fadeamount;
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
switch (fademode)
2020-01-01 21:29:24 +01:00
{
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
case FADE_START_FADEOUT:
for (size_t i = 0; i < SDL_arraysize(fadebars); i++)
2020-01-01 21:29:24 +01:00
{
2023-05-22 21:06:50 +02:00
fadebars[i] = -(int)(fRandom() * 12) * 8;
2020-01-01 21:29:24 +01:00
}
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
setfade(0);
fademode = FADE_FADING_OUT;
break;
case FADE_FADING_OUT:
fadeamount += 24;
if (fadeamount > 416)
2020-01-01 21:29:24 +01:00
{
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
fademode = FADE_FULLY_BLACK;
2020-01-01 21:29:24 +01:00
}
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
break;
case FADE_START_FADEIN:
for (size_t i = 0; i < SDL_arraysize(fadebars); i++)
2020-01-01 21:29:24 +01:00
{
2023-05-22 21:06:50 +02:00
fadebars[i] = 320 + (int)(fRandom() * 12) * 8;
2020-01-01 21:29:24 +01:00
}
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
setfade(416);
fademode = FADE_FADING_IN;
break;
case FADE_FADING_IN:
fadeamount -= 24;
if (fadeamount <= 0)
2020-01-01 21:29:24 +01:00
{
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
fademode = FADE_NONE;
2020-01-01 21:29:24 +01:00
}
break;
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
case FADE_NONE:
case FADE_FULLY_BLACK:
break;
2020-01-01 21:29:24 +01:00
}
}
void Graphics::setfade(const int amount)
{
fadeamount = amount;
oldfadeamount = amount;
}
void Graphics::drawmenu(int cr, int cg, int cb, enum Menu::MenuName menu)
2020-01-01 21:29:24 +01:00
{
/* The MenuName is only used for some special cases,
* like the levels list and the language screen. */
bool language_screen = menu == Menu::language && !loc::languagelist.empty();
unsigned int twocol_voptions;
if (language_screen)
{
size_t n_options = game.menuoptions.size();
2023-05-22 21:06:50 +02:00
twocol_voptions = n_options - (n_options / 2);
}
for (size_t i = 0; i < game.menuoptions.size(); i++)
2020-01-01 21:29:24 +01:00
{
MenuOption& opt = game.menuoptions[i];
int fr, fg, fb;
if (opt.active)
2020-01-01 21:29:24 +01:00
{
// Color it normally
fr = cr;
fg = cg;
fb = cb;
2020-01-01 21:29:24 +01:00
}
else
{
// Color it gray
fr = 128;
fg = 128;
fb = 128;
}
int x, y;
if (language_screen)
{
int name_len = font::len(opt.print_flags, opt.text);
2023-05-22 21:06:50 +02:00
x = (i < twocol_voptions ? 80 : 240) - name_len / 2;
y = 36 + (i % twocol_voptions) * 12;
}
else
{
2023-05-22 21:06:50 +02:00
x = i * game.menuspacing + game.menuxoff;
y = 140 + i * 12 + game.menuyoff;
}
if (menu == Menu::levellist)
{
size_t separator;
if (cl.ListOfMetaData.size() > 8)
{
separator = 3;
}
else
{
separator = 1;
}
if (game.menuoptions.size() - i <= separator)
2020-01-01 21:29:24 +01:00
{
// We're on "next page", "previous page", or "return to menu". Draw them separated by a bit
y += 8;
2020-01-01 21:29:24 +01:00
}
else
{
// Get out of the way of the level descriptions
y += 4;
2020-01-01 21:29:24 +01:00
}
}
if (menu == Menu::translator_options_cutscenetest)
{
size_t separator = 4;
if (game.menuoptions.size() - i <= separator)
{
y += 4;
}
}
2020-01-01 21:29:24 +01:00
char buffer[MENU_TEXT_BYTES];
if ((int) i == game.currentmenuoption && game.slidermode == SLIDER_NONE)
2020-01-01 21:29:24 +01:00
{
std::string opt_text;
if (opt.active)
2020-01-01 21:29:24 +01:00
{
// Uppercase the text
opt_text = loc::toupper(opt.text);
2020-01-01 21:29:24 +01:00
}
else
{
opt_text = loc::remove_toupper_escape_chars(opt.text);
}
vformat_buf(buffer, sizeof(buffer), loc::get_langmeta()->menu_select.c_str(), "label:str", opt_text.c_str());
// Account for brackets
2023-05-22 21:06:50 +02:00
x -= (font::len(opt.print_flags, buffer) - font::len(opt.print_flags, opt_text.c_str())) / 2;
2020-01-01 21:29:24 +01:00
}
else
{
SDL_strlcpy(buffer, loc::remove_toupper_escape_chars(opt.text).c_str(), sizeof(buffer));
2020-01-01 21:29:24 +01:00
}
font::print(opt.print_flags, x, y, buffer, fr, fg, fb);
2020-01-01 21:29:24 +01:00
}
}
void Graphics::drawcoloredtile(
const int x, const int y,
const int t,
const int r, const int g, const int b
) {
draw_grid_tile(grphx.im_tiles_white, t, x, y, tiles_rect.w, tiles_rect.h, r, g, b);
2020-01-01 21:29:24 +01:00
}
bool Graphics::Hitest(SDL_Surface* surface1, SDL_Point p1, SDL_Surface* surface2, SDL_Point p2)
2020-01-01 21:29:24 +01:00
{
//find rectangle where they intersect:
int r1_left = p1.x;
int r1_right = r1_left + surface1->w;
int r2_left = p2.x;
int r2_right = r2_left + surface2->w;
int r1_bottom = p1.y;
int r1_top = p1.y + surface1->h;
int r2_bottom = p2.y;
int r2_top = p2.y + surface2->h;
SDL_Rect rect1 = {p1.x, p1.y, surface1->w, surface1->h};
SDL_Rect rect2 = {p2.x, p2.y, surface2->w, surface2->h};
bool intersection = help.intersects(rect1, rect2);
2020-01-01 21:29:24 +01:00
if(intersection)
{
int r3_left = SDL_max(r1_left, r2_left);
int r3_top = SDL_min(r1_top, r2_top);
int r3_right = SDL_min(r1_right, r2_right);
int r3_bottom= SDL_max(r1_bottom, r2_bottom);
2020-01-01 21:29:24 +01:00
//for every pixel inside rectangle
for(int x = r3_left; x < r3_right; x++)
{
for(int y = r3_bottom; y < r3_top; y++)
{
const SDL_Color pixel1 = ReadPixel(surface1, x - p1.x, y - p1.y);
const SDL_Color pixel2 = ReadPixel(surface2, x - p2.x, y - p2.y);
/* INTENTIONAL BUG! In previous versions, the game mistakenly
* checked the red channel, not the alpha channel.
* We preserve it here because some people abuse this. */
if (pixel1.r != 0 && pixel2.r != 0)
2020-01-01 21:29:24 +01:00
{
return true;
}
}
}
}
return false;
2020-01-01 21:29:24 +01:00
}
void Graphics::drawgravityline(const int t, const int x, const int y, const int w, const int h)
2020-01-01 21:29:24 +01:00
{
if (!INBOUNDS_VEC(t, obj.entities))
{
WHINE_ONCE("drawgravityline() out-of-bounds!");
return;
}
if (obj.entities[t].life == 0)
2020-01-01 21:29:24 +01:00
{
if (game.noflashingmode)
{
set_color(200 - 20, 200 - 20, 200 - 20);
draw_line(x, y, x + w, y + h);
return;
}
2020-01-01 21:29:24 +01:00
switch(linestate)
{
case 0:
set_color(200 - 20, 200 - 20, 200 - 20);
2020-01-01 21:29:24 +01:00
break;
case 1:
set_color(245 - 30, 245 - 30, 225 - 30);
2020-01-01 21:29:24 +01:00
break;
case 2:
set_color(225 - 30, 245 - 30, 245 - 30);
2020-01-01 21:29:24 +01:00
break;
case 3:
set_color(200 - 20, 200 - 20, 164 - 10);
2020-01-01 21:29:24 +01:00
break;
case 4:
set_color(196 - 20, 255 - 30, 224 - 20);
2020-01-01 21:29:24 +01:00
break;
case 5:
set_color(196 - 20, 235 - 30, 205 - 20);
2020-01-01 21:29:24 +01:00
break;
case 6:
set_color(164 - 10, 164 - 10, 164 - 10);
2020-01-01 21:29:24 +01:00
break;
case 7:
set_color(205 - 20, 245 - 30, 225 - 30);
2020-01-01 21:29:24 +01:00
break;
case 8:
set_color(225 - 30, 255 - 30, 205 - 20);
2020-01-01 21:29:24 +01:00
break;
case 9:
set_color(245 - 30, 245 - 30, 245 - 30);
2020-01-01 21:29:24 +01:00
break;
}
}
else
{
set_color(96, 96, 96);
2020-01-01 21:29:24 +01:00
}
draw_line(x, y, x + w, y + h);
2020-01-01 21:29:24 +01:00
}
void Graphics::drawtrophytext(void)
2020-01-01 21:29:24 +01:00
{
Implement first font::print function, fix most fading of colored glyphs There has always been a mess of different print functions that all had slightly different specifics and called each other: Print(x, y, text, r, g, b, cen) nothing special here, just does what the arguments say PrintAlpha(x, y, text, r, g, b, a, cen) just Print but with an alpha argument PrintWrap(x, y, text, r, g, b, cen, linespacing, maxwidth) added for wordwrapping, heavily used now bprint(x, y, text, r, g, b, cen) prints an outline, then just PrintAlpha bprintalpha(x, y, text, r, g, b, a, cen) just bprint but with an alpha argument bigprint(x, y, text, r, g, b, cen, sc) nothing special here, just does what the arguments say bigbprint(x, y, text, r, g, b, cen, sc) prints an outline, then just bigprint bigrprint(x, y, text, r, g, b, cen, sc) right-aligns text, unless cen is given in which case it just centers text like other functions already do? bigbrprint(x, y, text, r, g, b, cen, sc) prints an outline, then just bigrprint We need even more specifics with the new font system: we need to be able to specify whether CJK characters should be vertically centered or stick out on the top/bottom, and we sometimes need to pass in brightness variables for colored glyphs. And text printing functions now fit better in Font.cpp anyway. So there's now a big overhaul of print functions: all these functions will be replaced by font::print and font::print_wrap (the former of which now exists). These take flags as their first argument, which can be 0 for a basic left-aligned print, PR_CEN for centered text (set X to -1!!!) PR_BOR for a border (instead of functions like bprint and bigbprint), PR_2X, PR_3X etc for scaling, and these can be combined with |. Some text, for example [Press ESC to return to editor], fades in/out using the alpha value, which is passed to the print function. In some other places (like Press ENTER to teleport, textboxes, trophy text...) text can fade in or out by direct changes to the RGB values. This means regular color-adjusted white text can change color, but colored button glyphs can't, since there's no way to know in the print system what the maximum RGB values of a specific textbox are supposed to be, so the only thing it can do is draw the button glyphs at full brightness, which looks bad. Therefore, you can now also pass in the brightness value via the flags, with PR_COLORGLYPH_BRI(255).
2023-01-06 04:43:21 +01:00
int brightness;
2020-01-01 21:29:24 +01:00
if (obj.trophytext < 15)
{
const int usethismult = lerp(obj.oldtrophytext, obj.trophytext);
2023-05-22 21:06:50 +02:00
brightness = (usethismult / 15.0) * 255;
2020-01-01 21:29:24 +01:00
}
else
{
Implement first font::print function, fix most fading of colored glyphs There has always been a mess of different print functions that all had slightly different specifics and called each other: Print(x, y, text, r, g, b, cen) nothing special here, just does what the arguments say PrintAlpha(x, y, text, r, g, b, a, cen) just Print but with an alpha argument PrintWrap(x, y, text, r, g, b, cen, linespacing, maxwidth) added for wordwrapping, heavily used now bprint(x, y, text, r, g, b, cen) prints an outline, then just PrintAlpha bprintalpha(x, y, text, r, g, b, a, cen) just bprint but with an alpha argument bigprint(x, y, text, r, g, b, cen, sc) nothing special here, just does what the arguments say bigbprint(x, y, text, r, g, b, cen, sc) prints an outline, then just bigprint bigrprint(x, y, text, r, g, b, cen, sc) right-aligns text, unless cen is given in which case it just centers text like other functions already do? bigbrprint(x, y, text, r, g, b, cen, sc) prints an outline, then just bigrprint We need even more specifics with the new font system: we need to be able to specify whether CJK characters should be vertically centered or stick out on the top/bottom, and we sometimes need to pass in brightness variables for colored glyphs. And text printing functions now fit better in Font.cpp anyway. So there's now a big overhaul of print functions: all these functions will be replaced by font::print and font::print_wrap (the former of which now exists). These take flags as their first argument, which can be 0 for a basic left-aligned print, PR_CEN for centered text (set X to -1!!!) PR_BOR for a border (instead of functions like bprint and bigbprint), PR_2X, PR_3X etc for scaling, and these can be combined with |. Some text, for example [Press ESC to return to editor], fades in/out using the alpha value, which is passed to the print function. In some other places (like Press ENTER to teleport, textboxes, trophy text...) text can fade in or out by direct changes to the RGB values. This means regular color-adjusted white text can change color, but colored button glyphs can't, since there's no way to know in the print system what the maximum RGB values of a specific textbox are supposed to be, so the only thing it can do is draw the button glyphs at full brightness, which looks bad. Therefore, you can now also pass in the brightness value via the flags, with PR_COLORGLYPH_BRI(255).
2023-01-06 04:43:21 +01:00
brightness = 255;
2020-01-01 21:29:24 +01:00
}
/* These were originally all at the top of the screen, but might be too tight for localization.
* It probably makes sense to make them all have a top text now, but for now this is probably fine.
* Look at the Steam achievements, they have pretty logical titles that should probably be used. */
const char* top_text = NULL;
const char* bottom_text = NULL;
2020-01-01 21:29:24 +01:00
switch(obj.trophytype)
{
case 1:
top_text = loc::gettext("SPACE STATION 1 MASTERED");
bottom_text = loc::gettext("Obtain a V Rank in this Time Trial");
2020-01-01 21:29:24 +01:00
break;
case 2:
top_text = loc::gettext("LABORATORY MASTERED");
bottom_text = loc::gettext("Obtain a V Rank in this Time Trial");
2020-01-01 21:29:24 +01:00
break;
case 3:
top_text = loc::gettext("THE TOWER MASTERED");
bottom_text = loc::gettext("Obtain a V Rank in this Time Trial");
2020-01-01 21:29:24 +01:00
break;
case 4:
top_text = loc::gettext("SPACE STATION 2 MASTERED");
bottom_text = loc::gettext("Obtain a V Rank in this Time Trial");
2020-01-01 21:29:24 +01:00
break;
case 5:
top_text = loc::gettext("WARP ZONE MASTERED");
bottom_text = loc::gettext("Obtain a V Rank in this Time Trial");
2020-01-01 21:29:24 +01:00
break;
case 6:
top_text = loc::gettext("FINAL LEVEL MASTERED");
bottom_text = loc::gettext("Obtain a V Rank in this Time Trial");
2020-01-01 21:29:24 +01:00
break;
case 7:
top_text = loc::gettext("GAME COMPLETE");
bottom_text = loc::gettext_case("Complete the game", 1);
2020-01-01 21:29:24 +01:00
break;
case 8:
top_text = loc::gettext("FLIP MODE COMPLETE");
bottom_text = loc::gettext("Complete the game in flip mode");
2020-01-01 21:29:24 +01:00
break;
case 9:
bottom_text = loc::gettext("Win with less than 50 deaths");
2020-01-01 21:29:24 +01:00
break;
case 10:
bottom_text = loc::gettext("Win with less than 100 deaths");
2020-01-01 21:29:24 +01:00
break;
case 11:
bottom_text = loc::gettext("Win with less than 250 deaths");
2020-01-01 21:29:24 +01:00
break;
case 12:
bottom_text = loc::gettext("Win with less than 500 deaths");
2020-01-01 21:29:24 +01:00
break;
case 13:
bottom_text = loc::gettext("Last 5 seconds on the Super Gravitron");
2020-01-01 21:29:24 +01:00
break;
case 14:
bottom_text = loc::gettext("Last 10 seconds on the Super Gravitron");
2020-01-01 21:29:24 +01:00
break;
case 15:
bottom_text = loc::gettext("Last 15 seconds on the Super Gravitron");
2020-01-01 21:29:24 +01:00
break;
case 16:
bottom_text = loc::gettext("Last 20 seconds on the Super Gravitron");
2020-01-01 21:29:24 +01:00
break;
case 17:
bottom_text = loc::gettext("Last 30 seconds on the Super Gravitron");
2020-01-01 21:29:24 +01:00
break;
case 18:
bottom_text = loc::gettext("Last 1 minute on the Super Gravitron");
2020-01-01 21:29:24 +01:00
break;
case 20:
top_text = loc::gettext("MASTER OF THE UNIVERSE");
bottom_text = loc::gettext("Complete the game in no death mode");
2020-01-01 21:29:24 +01:00
break;
}
short lines;
if (top_text != NULL)
{
font::string_wordwrap(0, top_text, 304, &lines);
2023-05-22 21:06:50 +02:00
font::print_wrap(PR_CEN | PR_BRIGHTNESS(brightness) | PR_BOR, -1, 11 - (lines - 1) * 5, top_text, 196, 196, 255 - help.glow);
}
if (bottom_text != NULL)
{
font::string_wordwrap(0, bottom_text, 304, &lines);
2023-05-22 21:06:50 +02:00
font::print_wrap(PR_CEN | PR_BRIGHTNESS(brightness) | PR_BOR, -1, 221 - (lines - 1) * 5, bottom_text, 196, 196, 255 - help.glow);
}
2020-01-01 21:29:24 +01:00
}
void Graphics::drawentities(void)
2020-01-01 21:29:24 +01:00
{
const int yoff = map.towermode ? lerp(map.oldypos, map.ypos) : 0;
Fix crewmates being drawn behind other entities This fixes the draw order by drawing all other entities first, before then drawing all humanoids[1] after, including the player afterwards. This is actually a regression fix from #191. When I was testing this, I was thinking about where get a crewmate in front of another entity in the main game, other than the checkpoints in Intermission 1. And then I thought about the teleporters, because I remember the pre-Deep Space cutscene in Dimension Open looking funny because Vita ended up being behind the teleporter. (Actually, a lot of the cutscenes of Dimension Open look funny because of crewmates standing behind terminals.) So then I tried to get crewmates in front of teleporters. It actually turns out that you can't do it for most of them... except for Verdigris. And then that's what I realized why there was an oddity in WarpClass.cpp when I was removing the `active` system from the game - for some reason, the game put a hole in `obj.entities` between the teleporter and the player when loading the room Murdering Twinmaker. In a violation of Chesterton's Fence (the principle that you should understand something before removing it), I shrugged it off and decided "there's no way to support having holes with my new system, and having holes is probably bad anyway, so I'm going to remove this and move on". The fact that there wasn't any comments clarifying the mysterious code didn't help (but, this *was* 2.2 code after all; have you *seen* 2.2 code?!). And it turns out that this maneuver was done so Verdigris would fill that hole when he got created, and Verdigris being first before the teleporter would mean he would be drawn in front of the teleporter, instead of being behind it. So ever since b1b1474b7bbc3ceddea24f689a7ddb050cfe4490 got merged, there has actually been a regression from 2.2 where Verdigris got drawn behind the teleporter in Murdering Twinmaker, instead of properly being in front of it like in 2.2 and previous. This patch fixes that regression, but it actually properly fixes it instead of hacking around with the `active` system. Closes #426. [1]: I'm going to go on a rant here, so hear me out. It's not explicitly stated that the characters in VVVVVV are human. So, given this information, what do we call them? Well, the VVVVVV community (at least the custom levels one, I don't think the speedrunning community does this or is preoccupied with lore in the first place) decided to call them "villis", because of the roomname "The Villi People" - which is only one blunder in a series of awful headcanons based off of the assumption that the intent of Bennett Foddy (who named the roomnames) was to decree some sort of lore to the game. Another one being "Verdigris can't flip" because of "Green Dudes Can't Flip". Then an OC (original character) got named based off of "The Voon Show" too. And so on and so forth.
2020-11-01 07:23:40 +01:00
if (!map.custommode)
{
Fix crewmates being drawn behind other entities This fixes the draw order by drawing all other entities first, before then drawing all humanoids[1] after, including the player afterwards. This is actually a regression fix from #191. When I was testing this, I was thinking about where get a crewmate in front of another entity in the main game, other than the checkpoints in Intermission 1. And then I thought about the teleporters, because I remember the pre-Deep Space cutscene in Dimension Open looking funny because Vita ended up being behind the teleporter. (Actually, a lot of the cutscenes of Dimension Open look funny because of crewmates standing behind terminals.) So then I tried to get crewmates in front of teleporters. It actually turns out that you can't do it for most of them... except for Verdigris. And then that's what I realized why there was an oddity in WarpClass.cpp when I was removing the `active` system from the game - for some reason, the game put a hole in `obj.entities` between the teleporter and the player when loading the room Murdering Twinmaker. In a violation of Chesterton's Fence (the principle that you should understand something before removing it), I shrugged it off and decided "there's no way to support having holes with my new system, and having holes is probably bad anyway, so I'm going to remove this and move on". The fact that there wasn't any comments clarifying the mysterious code didn't help (but, this *was* 2.2 code after all; have you *seen* 2.2 code?!). And it turns out that this maneuver was done so Verdigris would fill that hole when he got created, and Verdigris being first before the teleporter would mean he would be drawn in front of the teleporter, instead of being behind it. So ever since b1b1474b7bbc3ceddea24f689a7ddb050cfe4490 got merged, there has actually been a regression from 2.2 where Verdigris got drawn behind the teleporter in Murdering Twinmaker, instead of properly being in front of it like in 2.2 and previous. This patch fixes that regression, but it actually properly fixes it instead of hacking around with the `active` system. Closes #426. [1]: I'm going to go on a rant here, so hear me out. It's not explicitly stated that the characters in VVVVVV are human. So, given this information, what do we call them? Well, the VVVVVV community (at least the custom levels one, I don't think the speedrunning community does this or is preoccupied with lore in the first place) decided to call them "villis", because of the roomname "The Villi People" - which is only one blunder in a series of awful headcanons based off of the assumption that the intent of Bennett Foddy (who named the roomnames) was to decree some sort of lore to the game. Another one being "Verdigris can't flip" because of "Green Dudes Can't Flip". Then an OC (original character) got named based off of "The Voon Show" too. And so on and so forth.
2020-11-01 07:23:40 +01:00
for (int i = obj.entities.size() - 1; i >= 0; i--)
{
if (!obj.entities[i].ishumanoid())
{
drawentity(i, yoff);
}
}
for (int i = obj.entities.size() - 1; i >= 0; i--)
{
if (obj.entities[i].ishumanoid())
{
drawentity(i, yoff);
}
}
}
else
{
for (int i = obj.entities.size() - 1; i >= 0; i--)
{
drawentity(i, yoff);
}
}
}
void Graphics::drawentity(const int i, const int yoff)
{
if (!INBOUNDS_VEC(i, obj.entities))
{
WHINE_ONCE("drawentity() out-of-bounds!");
return;
}
if (obj.entities[i].invis)
{
return;
}
SDL_Point tpoint;
2020-01-01 21:29:24 +01:00
SDL_Rect drawRect;
bool custom_gray;
// Special case for gray Warp Zone tileset!
if (map.custommode)
{
const RoomProperty* const room = cl.getroomprop(game.roomx - 100, game.roomy - 100);
custom_gray = room->tileset == 3 && room->tilecol == 6;
}
else
{
custom_gray = false;
}
SDL_Texture* sprites = flipmode ? grphx.im_flipsprites : grphx.im_sprites;
SDL_Texture* tiles = (map.custommode && !map.finalmode) ? grphx.im_entcolours : grphx.im_tiles;
SDL_Texture* tiles_tint = (map.custommode && !map.finalmode) ? grphx.im_entcolours_tint : grphx.im_tiles_tint;
const int xp = lerp(obj.entities[i].lerpoldxp, obj.entities[i].xp);
const int yp = lerp(obj.entities[i].lerpoldyp, obj.entities[i].yp);
switch (obj.entities[i].size)
{
case 0:
{
// Sprites
tpoint.x = xp;
tpoint.y = yp - yoff;
const SDL_Color ct = obj.entities[i].realcol;
drawRect = sprites_rect;
drawRect.x += tpoint.x;
drawRect.y += tpoint.y;
draw_grid_tile(sprites, obj.entities[i].drawframe, drawRect.x, drawRect.y, 32, 32, ct);
2023-05-22 21:06:50 +02:00
// screenwrapping!
SDL_Point wrappedPoint;
bool wrapX = false;
bool wrapY = false;
wrappedPoint.x = tpoint.x;
if (tpoint.x < 0)
{
wrapX = true;
wrappedPoint.x += 320;
}
else if (tpoint.x > 288)
{
wrapX = true;
wrappedPoint.x -= 320;
}
wrappedPoint.y = tpoint.y;
if (tpoint.y < 8)
{
wrapY = true;
wrappedPoint.y += 232;
}
else if (tpoint.y > 200)
{
wrapY = true;
wrappedPoint.y -= 232;
}
const bool isInWrappingAreaOfTower = map.towermode && !map.minitowermode && map.ypos >= 500 && map.ypos <= 5000;
if (wrapX && (map.warpx || isInWrappingAreaOfTower))
{
drawRect = sprites_rect;
drawRect.x += wrappedPoint.x;
drawRect.y += tpoint.y;
draw_grid_tile(sprites, obj.entities[i].drawframe, drawRect.x, drawRect.y, 32, 32, ct);
}
if (wrapY && map.warpy)
{
drawRect = sprites_rect;
drawRect.x += tpoint.x;
drawRect.y += wrappedPoint.y;
draw_grid_tile(sprites, obj.entities[i].drawframe, drawRect.x, drawRect.y, 32, 32, ct);
}
if (wrapX && wrapY && map.warpx && map.warpy)
{
drawRect = sprites_rect;
drawRect.x += wrappedPoint.x;
drawRect.y += wrappedPoint.y;
draw_grid_tile(sprites, obj.entities[i].drawframe, drawRect.x, drawRect.y, 32, 32, ct);
}
break;
}
case 1:
// Tiles
tpoint.x = xp;
tpoint.y = yp - yoff;
drawRect = tiles_rect;
drawRect.x += tpoint.x;
drawRect.y += tpoint.y;
draw_grid_tile(grphx.im_tiles, obj.entities[i].drawframe, drawRect.x, drawRect.y, 8, 8);
break;
case 2:
case 8:
{
// Special: Moving platform, 4 tiles or 8 tiles
tpoint.x = xp;
tpoint.y = yp - yoff;
int thiswidth = 4;
if (obj.entities[i].size == 8)
{
thiswidth = 8;
}
for (int ii = 0; ii < thiswidth; ii++)
{
drawRect = tiles_rect;
drawRect.x += tpoint.x;
drawRect.y += tpoint.y;
drawRect.x += 8 * ii;
if (custom_gray)
{
draw_grid_tile(tiles_tint, obj.entities[i].drawframe, drawRect.x, drawRect.y, 8, 8);
}
else
2020-01-01 21:29:24 +01:00
{
draw_grid_tile(tiles, obj.entities[i].drawframe, drawRect.x, drawRect.y, 8, 8);
2020-01-01 21:29:24 +01:00
}
}
break;
}
2023-05-22 21:06:50 +02:00
case 3: // Big chunky pixels!
fill_rect(xp, yp - yoff, 4, 4, obj.entities[i].realcol);
break;
2023-05-22 21:06:50 +02:00
case 4: // Small pickups
{
const SDL_Color color = obj.entities[i].realcol;
drawcoloredtile(xp, yp - yoff, obj.entities[i].tile, color.r, color.g, color.b);
break;
}
2023-05-22 21:06:50 +02:00
case 5: // Horizontal Line
{
int oldw = obj.entities[i].w;
if ((game.swngame == SWN_START_GRAVITRON_STEP_2 || kludgeswnlinewidth)
&& obj.getlineat(84 - 32) == i)
{
oldw -= 24;
}
drawgravityline(i, xp, yp - yoff, lerp(oldw, obj.entities[i].w) - 1, 0);
break;
}
2023-05-22 21:06:50 +02:00
case 6: // Vertical Line
drawgravityline(i, xp, yp - yoff, 0, obj.entities[i].h - 1);
break;
2023-05-22 21:06:50 +02:00
case 7: // Teleporter
drawtele(xp, yp - yoff, obj.entities[i].drawframe, obj.entities[i].realcol);
break;
2023-05-22 21:06:50 +02:00
// case 8: // Special: Moving platform, 8 tiles
// Note: This code is in the 4-tile code
break;
2023-05-22 21:06:50 +02:00
case 9: // Really Big Sprite! (2x2)
{
const SDL_Color ct = obj.entities[i].realcol;
tpoint.x = xp;
tpoint.y = yp - yoff;
drawRect = sprites_rect;
drawRect.x += tpoint.x;
drawRect.y += tpoint.y;
draw_grid_tile(sprites, obj.entities[i].drawframe, drawRect.x, drawRect.y, 32, 32, ct);
2023-05-22 21:06:50 +02:00
tpoint.x = xp + 32;
tpoint.y = yp - yoff;
2023-05-22 21:06:50 +02:00
drawRect = sprites_rect;
drawRect.x += tpoint.x;
drawRect.y += tpoint.y;
draw_grid_tile(sprites, obj.entities[i].drawframe + 1, drawRect.x, drawRect.y, 32, 32, ct);
tpoint.x = xp;
2023-05-22 21:06:50 +02:00
tpoint.y = yp + 32 - yoff;
drawRect = sprites_rect;
drawRect.x += tpoint.x;
drawRect.y += tpoint.y;
draw_grid_tile(sprites, obj.entities[i].drawframe + 12, drawRect.x, drawRect.y, 32, 32, ct);
2023-05-22 21:06:50 +02:00
tpoint.x = xp + 32;
tpoint.y = yp + 32 - yoff;
drawRect = sprites_rect;
drawRect.x += tpoint.x;
drawRect.y += tpoint.y;
draw_grid_tile(sprites, obj.entities[i].drawframe + 13, drawRect.x, drawRect.y, 32, 32, ct);
break;
}
2023-05-22 21:06:50 +02:00
case 10: // 2x1 Sprite
{
const SDL_Color ct = obj.entities[i].realcol;
tpoint.x = xp;
tpoint.y = yp - yoff;
2023-05-22 21:06:50 +02:00
drawRect = sprites_rect;
drawRect.x += tpoint.x;
drawRect.y += tpoint.y;
draw_grid_tile(sprites, obj.entities[i].drawframe, drawRect.x, drawRect.y, 32, 32, ct);
2023-05-22 21:06:50 +02:00
tpoint.x = xp + 32;
tpoint.y = yp - yoff;
2023-05-22 21:06:50 +02:00
drawRect = sprites_rect;
drawRect.x += tpoint.x;
drawRect.y += tpoint.y;
draw_grid_tile(sprites, obj.entities[i].drawframe + 1, drawRect.x, drawRect.y, 32, 32, ct);
break;
}
2023-05-22 21:06:50 +02:00
case 11: // The fucking elephant
drawimagecol(IMAGE_ELEPHANT, xp, yp - yoff, obj.entities[i].realcol);
break;
2023-05-22 21:06:50 +02:00
case 12: // Regular sprites that don't wrap
{
tpoint.x = xp;
tpoint.y = yp - yoff;
const SDL_Color ct = obj.entities[i].realcol;
2023-05-22 21:06:50 +02:00
drawRect = sprites_rect;
drawRect.x += tpoint.x;
drawRect.y += tpoint.y;
draw_grid_tile(sprites, obj.entities[i].drawframe, drawRect.x, drawRect.y, 32, 32, ct);
2023-05-22 21:06:50 +02:00
// if we're outside the screen, we need to draw indicators
if (obj.entities[i].xp < -20 && obj.entities[i].vx > 0)
{
if (obj.entities[i].xp < -100)
{
2023-05-22 21:06:50 +02:00
tpoint.x = -5 + (int) (-xp / 10);
}
else
{
tpoint.x = 5;
}
2023-05-22 21:06:50 +02:00
tpoint.y = tpoint.y + 4;
drawRect = tiles_rect;
drawRect.x += tpoint.x;
drawRect.y += tpoint.y;
draw_grid_tile(grphx.im_tiles_white, 1167, drawRect.x, drawRect.y, 8, 8, ct);
}
else if (obj.entities[i].xp > 340 && obj.entities[i].vx < 0)
{
if (obj.entities[i].xp > 420)
{
2023-05-22 21:06:50 +02:00
tpoint.x = 320 - (int) ((xp - 320) / 10);
}
else
{
tpoint.x = 310;
}
tpoint.y = tpoint.y+4;
drawRect = tiles_rect;
drawRect.x += tpoint.x;
drawRect.y += tpoint.y;
draw_grid_tile(grphx.im_tiles_white, 1166, drawRect.x, drawRect.y, 8, 8, ct);
}
break;
}
case 13:
{
2023-05-22 21:06:50 +02:00
// Special for epilogue: huge hero!
draw_grid_tile(grphx.im_sprites, obj.entities[i].drawframe, xp, yp - yoff, sprites_rect.w, sprites_rect.h, obj.entities[i].realcol, 6, 6);
break;
}
}
2020-01-01 21:29:24 +01:00
}
void Graphics::drawbackground( int t )
2020-01-01 21:29:24 +01:00
{
switch(t)
{
case 1:
2023-05-22 21:06:50 +02:00
// Starfield
fill_rect(0, 0, 0);
for (int i = 0; i < numstars; i++)
2020-01-01 21:29:24 +01:00
{
SDL_Rect star_rect = stars[i];
star_rect.x = lerp(star_rect.x + starsspeed[i], star_rect.x);
2020-01-01 21:29:24 +01:00
if (starsspeed[i] <= 6)
{
fill_rect(&star_rect, getRGB(0x22,0x22,0x22));
2020-01-01 21:29:24 +01:00
}
else
{
fill_rect(&star_rect, getRGB(0x55,0x55,0x55));
2020-01-01 21:29:24 +01:00
}
}
break;
case 2:
{
SDL_Color bcol;
SDL_Color bcol2;
SDL_zero(bcol);
SDL_zero(bcol2);
2023-05-22 21:06:50 +02:00
// Lab
switch (rcol)
{
// Akward ordering to match tileset
case 0:
bcol2 = getRGB(0, 16 * backboxmult, 16 * backboxmult);
break; // Cyan
case 1:
bcol2 = getRGB(16 * backboxmult, 0, 0);
break; // Red
case 2:
bcol2 = getRGB(16 * backboxmult, 0, 16 * backboxmult);
break; // Purple
case 3:
bcol2 = getRGB(0, 0, 16 * backboxmult);
break; // Blue
case 4:
bcol2 = getRGB(16 * backboxmult, 16 * backboxmult, 0);
break; // Yellow
case 5:
bcol2 = getRGB(0, 16 * backboxmult, 0);
break; // Green
case 6:
// crazy case
switch (spcol)
2020-01-01 21:29:24 +01:00
{
case 0:
2023-05-22 21:06:50 +02:00
bcol2 = getRGB(0, 16 * backboxmult, 16 * backboxmult);
break; // Cyan
2020-01-01 21:29:24 +01:00
case 1:
2023-05-22 21:06:50 +02:00
bcol2 = getRGB(0, (spcoldel + 1) * backboxmult, 16 * backboxmult);
break; // Cyan
2020-01-01 21:29:24 +01:00
case 2:
2023-05-22 21:06:50 +02:00
bcol2 = getRGB(0, 0, 16 * backboxmult);
break; // Blue
2020-01-01 21:29:24 +01:00
case 3:
2023-05-22 21:06:50 +02:00
bcol2 = getRGB((16 - spcoldel) * backboxmult, 0, 16 * backboxmult);
break; // Blue
2020-01-01 21:29:24 +01:00
case 4:
2023-05-22 21:06:50 +02:00
bcol2 = getRGB(16 * backboxmult, 0, 16 * backboxmult);
break; // Purple
2020-01-01 21:29:24 +01:00
case 5:
2023-05-22 21:06:50 +02:00
bcol2 = getRGB(16 * backboxmult, 0, (spcoldel + 1) * backboxmult);
break; // Purple
2020-01-01 21:29:24 +01:00
case 6:
2023-05-22 21:06:50 +02:00
bcol2 = getRGB(16 * backboxmult, 0, 0);
break; // Red
case 7:
bcol2 = getRGB(16 * backboxmult, (16 - spcoldel) * backboxmult, 0);
break; // Red
case 8:
bcol2 = getRGB(16 * backboxmult, 16 * backboxmult, 0);
break; // Yellow
case 9:
bcol2 = getRGB((spcoldel + 1) * backboxmult, 16 * backboxmult, 0);
break; // Yellow
case 10:
bcol2 = getRGB(0, 16 * backboxmult, 0);
break; // Green
case 11:
bcol2 = getRGB(0, 16 * backboxmult, (16 - spcoldel) * backboxmult);
break; // Green
}
2020-01-01 21:29:24 +01:00
break;
}
fill_rect(bcol2);
2020-01-01 21:29:24 +01:00
for (int i = 0; i < numbackboxes; i++)
2020-01-01 21:29:24 +01:00
{
2023-05-22 21:06:50 +02:00
switch (rcol)
2020-01-01 21:29:24 +01:00
{
2023-05-22 21:06:50 +02:00
// Akward ordering to match tileset
2020-01-01 21:29:24 +01:00
case 0:
2023-05-22 21:06:50 +02:00
bcol = getRGB(16, 128 * backboxmult, 128 * backboxmult);
break; // Cyan
2020-01-01 21:29:24 +01:00
case 1:
2023-05-22 21:06:50 +02:00
bcol = getRGB(128 * backboxmult, 16, 16);
break; // Red
2020-01-01 21:29:24 +01:00
case 2:
2023-05-22 21:06:50 +02:00
bcol = getRGB(128 * backboxmult, 16, 128 * backboxmult);
break; // Purple
2020-01-01 21:29:24 +01:00
case 3:
2023-05-22 21:06:50 +02:00
bcol = getRGB(16, 16, 128 * backboxmult);
break; // Blue
2020-01-01 21:29:24 +01:00
case 4:
2023-05-22 21:06:50 +02:00
bcol = getRGB(128 * backboxmult, 128 * backboxmult, 16);
break; // Yellow
2020-01-01 21:29:24 +01:00
case 5:
bcol = getRGB(16, 128 * backboxmult, 16);
2023-05-22 21:06:50 +02:00
break; // Green
2020-01-01 21:29:24 +01:00
case 6:
2023-05-22 21:06:50 +02:00
// crazy case
switch (spcol)
2020-01-01 21:29:24 +01:00
{
case 0:
2023-05-22 21:06:50 +02:00
bcol = getRGB(16, 128 * backboxmult, 128 * backboxmult);
break; // Cyan
2020-01-01 21:29:24 +01:00
case 1:
2023-05-22 21:06:50 +02:00
bcol = getRGB(16, ((spcoldel + 1) * 8) * backboxmult, 128 * backboxmult);
break; // Cyan
2020-01-01 21:29:24 +01:00
case 2:
2023-05-22 21:06:50 +02:00
bcol = getRGB(16, 16, 128 * backboxmult);
break; // Blue
2020-01-01 21:29:24 +01:00
case 3:
2023-05-22 21:06:50 +02:00
bcol = getRGB((128 - (spcoldel * 8)) * backboxmult, 16, 128 * backboxmult);
break; // Blue
2020-01-01 21:29:24 +01:00
case 4:
2023-05-22 21:06:50 +02:00
bcol = getRGB(128 * backboxmult, 16, 128 * backboxmult);
break; // Purple
2020-01-01 21:29:24 +01:00
case 5:
2023-05-22 21:06:50 +02:00
bcol = getRGB(128 * backboxmult, 16, ((spcoldel + 1) * 8) * backboxmult);
break; // Purple
2020-01-01 21:29:24 +01:00
case 6:
2023-05-22 21:06:50 +02:00
bcol = getRGB(128 * backboxmult, 16, 16);
break; // Red
2020-01-01 21:29:24 +01:00
case 7:
2023-05-22 21:06:50 +02:00
bcol = getRGB(128 * backboxmult, (128 - (spcoldel * 8)) * backboxmult, 16);
break; // Red
2020-01-01 21:29:24 +01:00
case 8:
2023-05-22 21:06:50 +02:00
bcol = getRGB(128 * backboxmult, 128 * backboxmult, 16);
break; // Yellow
2020-01-01 21:29:24 +01:00
case 9:
2023-05-22 21:06:50 +02:00
bcol = getRGB(((spcoldel + 1) * 8) * backboxmult, 128 * backboxmult, 16);
break; // Yellow
2020-01-01 21:29:24 +01:00
case 10:
bcol = getRGB(16, 128 * backboxmult, 16);
2023-05-22 21:06:50 +02:00
break; // Green
2020-01-01 21:29:24 +01:00
case 11:
2023-05-22 21:06:50 +02:00
bcol = getRGB(16, 128 * backboxmult, (128 - (spcoldel * 8)) * backboxmult);
break; // Green
2020-01-01 21:29:24 +01:00
}
break;
}
SDL_Rect backboxrect = backboxes[i];
backboxrect.x = lerp(backboxes[i].x - backboxvx[i], backboxes[i].x);
backboxrect.y = lerp(backboxes[i].y - backboxvy[i], backboxes[i].y);
fill_rect(&backboxrect, bcol);
2023-05-22 21:06:50 +02:00
backboxrect.x++;
backboxrect.y++;
backboxrect.w -= 2;
backboxrect.h -= 2;
fill_rect(&backboxrect, bcol2);
2020-01-01 21:29:24 +01:00
}
break;
}
2020-01-01 21:29:24 +01:00
case 3: //Warp zone (horizontal)
{
clear();
const int offset = (int) lerp(-3, 0);
const SDL_Rect srcRect = {8 + offset, 0, SCREEN_WIDTH_PIXELS, SCREEN_HEIGHT_PIXELS};
copy_texture(backgroundTexture, &srcRect, NULL);
2020-01-01 21:29:24 +01:00
break;
}
2020-01-01 21:29:24 +01:00
case 4: //Warp zone (vertical)
{
clear();
const int offset = (int) lerp(-3, 0);
const SDL_Rect srcRect = {0, 8 + offset, SCREEN_WIDTH_PIXELS, SCREEN_HEIGHT_PIXELS};
copy_texture(backgroundTexture, &srcRect, NULL);
2020-01-01 21:29:24 +01:00
break;
}
2020-01-01 21:29:24 +01:00
case 5:
{
2023-05-22 21:06:50 +02:00
// Warp zone, central
SDL_Color warpbcol;
SDL_Color warpfcol;
2020-01-01 21:29:24 +01:00
switch(rcol)
{
2023-05-22 21:06:50 +02:00
// Akward ordering to match tileset
2020-01-01 21:29:24 +01:00
case 0:
warpbcol = getRGB(0x0A, 0x10, 0x0E);
warpfcol = getRGB(0x10, 0x22, 0x21);
2023-05-22 21:06:50 +02:00
break; // Cyan
2020-01-01 21:29:24 +01:00
case 1:
warpbcol = getRGB(0x11, 0x09, 0x0B);
warpfcol = getRGB(0x22, 0x10, 0x11);
2023-05-22 21:06:50 +02:00
break; // Red
2020-01-01 21:29:24 +01:00
case 2:
warpbcol = getRGB(0x0F, 0x0A, 0x10);
warpfcol = getRGB(0x22,0x10,0x22);
2023-05-22 21:06:50 +02:00
break; // Purple
2020-01-01 21:29:24 +01:00
case 3:
warpbcol = getRGB(0x0A, 0x0B, 0x10);
warpfcol = getRGB(0x10, 0x10, 0x22);
2023-05-22 21:06:50 +02:00
break; // Blue
2020-01-01 21:29:24 +01:00
case 4:
warpbcol = getRGB(0x10, 0x0D, 0x0A);
warpfcol = getRGB(0x22, 0x1E, 0x10);
2023-05-22 21:06:50 +02:00
break; // Yellow
2020-01-01 21:29:24 +01:00
case 5:
warpbcol = getRGB(0x0D, 0x10, 0x0A);
warpfcol = getRGB(0x14, 0x22, 0x10);
2023-05-22 21:06:50 +02:00
break; // Green
2020-01-01 21:29:24 +01:00
case 6:
warpbcol = getRGB(0x0A, 0x0A, 0x0A);
warpfcol = getRGB(0x12, 0x12, 0x12);
2023-05-22 21:06:50 +02:00
break; // Gray
default:
warpbcol = getRGB(0xFF, 0xFF, 0xFF);
warpfcol = getRGB(0xFF, 0xFF, 0xFF);
2020-01-01 21:29:24 +01:00
}
2023-05-22 21:06:50 +02:00
for (int i = 10; i >= 0; i--)
2020-01-01 21:29:24 +01:00
{
const int temp = (i * 16) + backoffset;
const SDL_Rect warprect = {160 - temp, 120 - temp, temp * 2, temp * 2};
2020-01-01 21:29:24 +01:00
if (i % 2 == warpskip)
{
fill_rect(&warprect, warpbcol);
2020-01-01 21:29:24 +01:00
}
else
{
fill_rect(&warprect, warpfcol);
2020-01-01 21:29:24 +01:00
}
}
break;
}
2020-01-01 21:29:24 +01:00
case 6:
2023-05-22 21:06:50 +02:00
// Final Starfield
fill_rect(0, 0, 0);
for (int i = 0; i < numstars; i++)
2020-01-01 21:29:24 +01:00
{
SDL_Rect star_rect = stars[i];
star_rect.y = lerp(star_rect.y + starsspeed[i], star_rect.y);
2020-01-01 21:29:24 +01:00
if (starsspeed[i] <= 8)
{
fill_rect(&star_rect, getRGB(0x22, 0x22, 0x22));
2020-01-01 21:29:24 +01:00
}
else
{
fill_rect(&star_rect, getRGB(0x55, 0x55, 0x55));
2020-01-01 21:29:24 +01:00
}
}
break;
case 7:
2023-05-22 21:06:50 +02:00
// Static, unscrolling section of the tower
2020-01-01 21:29:24 +01:00
for (int j = 0; j < 30; j++)
{
for (int i = 0; i < 40; i++)
{
drawtile3(i * 8, j * 8, map.tower.backat(i, j, 200), 15);
}
}
break;
case 8:
2023-05-22 21:06:50 +02:00
// Static, unscrolling section of the tower
2020-01-01 21:29:24 +01:00
for (int j = 0; j < 30; j++)
{
for (int i = 0; i < 40; i++)
{
drawtile3(i * 8, j * 8, map.tower.backat(i, j, 200), 10);
}
}
break;
case 9:
2023-05-22 21:06:50 +02:00
// Static, unscrolling section of the tower
2020-01-01 21:29:24 +01:00
for (int j = 0; j < 30; j++)
{
for (int i = 0; i < 40; i++)
{
drawtile3(i * 8, j * 8, map.tower.backat(i, j, 600), 0);
}
}
break;
default:
fill_rect(0, 0, 0);
2020-01-01 21:29:24 +01:00
break;
}
}
void Graphics::updatebackground(int t)
{
switch (t)
{
case 1:
2023-05-22 21:06:50 +02:00
// Starfield
for (int i = 0; i < numstars; i++)
{
stars[i].x -= starsspeed[i];
if (stars[i].x < -10)
{
stars[i].x += 340;
2023-06-06 15:23:37 +02:00
stars[i].y = (int) (fRandom() * 240);
starsspeed[i] = 4 + (int) (fRandom() * 4);
}
}
break;
case 2:
2023-05-22 21:06:50 +02:00
// Lab
if (rcol == 6)
{
2023-05-22 21:06:50 +02:00
// crazy caze
spcoldel--;
if (spcoldel <= 0)
{
spcoldel = 15;
spcol++;
if (spcol >= 12) spcol = 0;
}
}
for (int i = 0; i < numbackboxes; i++)
{
backboxes[i].x += backboxvx[i];
backboxes[i].y += backboxvy[i];
if (backboxes[i].x < -40)
{
backboxes[i].x = 320;
backboxes[i].y = fRandom() * 240;
}
if (backboxes[i].x > 320)
{
backboxes[i].x = -32;
backboxes[i].y = fRandom() * 240;
}
if (backboxes[i].y < -40)
{
backboxes[i].y = 240;
backboxes[i].x = fRandom() * 320;
}
if (backboxes[i].y > 260)
{
backboxes[i].y = -32;
backboxes[i].x = fRandom() * 320;
}
}
break;
2023-05-22 21:06:50 +02:00
case 3: // Warp zone (horizontal)
{
const int temp = 680 + (rcol * 3);
backoffset += 3;
if (backoffset >= 16) backoffset -= 16;
SDL_Texture* target = SDL_GetRenderTarget(gameScreen.m_renderer);
set_render_target(backgroundTexture);
if (backgrounddrawn)
{
scroll_texture(backgroundTexture, tempScrollingTexture, -3, 0);
for (int j = 0; j < 15; j++)
{
for (int i = 0; i < 2; i++)
{
2023-05-22 21:06:50 +02:00
drawtile2(317 - backoffset + (i * 16), (j * 16), temp + 40); // 20*16 = 320
drawtile2(317 - backoffset + (i * 16) + 8, (j * 16), temp + 41);
drawtile2(317 - backoffset + (i * 16), (j * 16) + 8, temp + 80);
drawtile2(317 - backoffset + (i * 16) + 8, (j * 16) + 8, temp + 81);
}
}
}
else
{
2023-05-22 21:06:50 +02:00
// draw the whole thing for the first time!
backoffset = 0;
clear();
for (int j = 0; j < 15; j++)
{
for (int i = 0; i < 21; i++)
{
drawtile2((i * 16) - backoffset - 3, (j * 16), temp + 40);
drawtile2((i * 16) - backoffset + 8 - 3, (j * 16), temp + 41);
drawtile2((i * 16) - backoffset - 3, (j * 16) + 8, temp + 80);
drawtile2((i * 16) - backoffset + 8 - 3, (j * 16) + 8, temp + 81);
}
}
backgrounddrawn = true;
}
set_render_target(target);
break;
}
2023-05-22 21:06:50 +02:00
case 4: // Warp zone (vertical)
{
const int temp = 760 + (rcol * 3);
backoffset += 3;
if (backoffset >= 16) backoffset -= 16;
SDL_Texture* target = SDL_GetRenderTarget(gameScreen.m_renderer);
set_render_target(backgroundTexture);
if (backgrounddrawn)
{
scroll_texture(backgroundTexture, tempScrollingTexture, 0, -3);
for (int j = 0; j < 2; j++)
{
for (int i = 0; i < 21; i++)
{
2023-05-22 21:06:50 +02:00
drawtile2((i * 16), 237 - backoffset + (j * 16), temp + 40); // 14*17=240 - 3
drawtile2((i * 16) + 8, 237 - backoffset + (j * 16), temp + 41);
drawtile2((i * 16), 237 - backoffset + (j * 16) + 8, temp + 80);
drawtile2((i * 16) + 8, 237 - backoffset + (j * 16) + 8, temp + 81);
}
}
}
else
{
2023-05-22 21:06:50 +02:00
// draw the whole thing for the first time!
backoffset = 0;
clear();
for (int j = 0; j < 16; j++)
{
for (int i = 0; i < 21; i++)
{
drawtile2((i * 16), (j * 16) - backoffset - 3, temp + 40);
drawtile2((i * 16) + 8, (j * 16) - backoffset - 3, temp + 41);
drawtile2((i * 16), (j * 16) - backoffset + 8 - 3, temp + 80);
drawtile2((i * 16) + 8, (j * 16) - backoffset + 8 - 3, temp + 81);
}
}
backgrounddrawn = true;
}
set_render_target(target);
break;
}
case 5:
2023-05-22 21:06:50 +02:00
// Warp zone, central
2023-05-22 21:06:50 +02:00
backoffset++;
if (backoffset >= 16)
{
backoffset -= 16;
warpskip = (warpskip + 1) % 2;
}
break;
case 6:
2023-05-22 21:06:50 +02:00
// Final Starfield
for (int i = 0; i < numstars; i++)
{
stars[i].y -= starsspeed[i];
if (stars[i].y < -10)
{
stars[i].y += 260;
stars[i].x = fRandom() * 320;
starsspeed[i] = 5 + (fRandom() * 5);
}
}
break;
}
}
void Graphics::drawmap(void)
2020-01-01 21:29:24 +01:00
{
if (!foregrounddrawn)
{
SDL_Texture* target = SDL_GetRenderTarget(gameScreen.m_renderer);
set_render_target(foregroundTexture);
set_blendmode(foregroundTexture, SDL_BLENDMODE_BLEND);
clear(0, 0, 0, 0);
for (int y = 0; y < 30; y++)
2020-01-01 21:29:24 +01:00
{
for (int x = 0; x < 40; x++)
2020-01-01 21:29:24 +01:00
{
int tile;
int tileset;
if (game.gamestate == EDITORMODE)
2020-01-01 21:29:24 +01:00
{
tile = cl.gettile(ed.levx, ed.levy, x, y);
tileset = (cl.getroomprop(ed.levx, ed.levy)->tileset == 0) ? 0 : 1;
2020-01-01 21:29:24 +01:00
}
else
2020-01-01 21:29:24 +01:00
{
tile = map.contents[TILE_IDX(x, y)];
tileset = map.tileset;
2020-01-01 21:29:24 +01:00
}
if (tile > 0)
2020-01-01 21:29:24 +01:00
{
if (tileset == 0)
{
drawtile(x * 8, y * 8, tile);
}
else if (tileset == 1)
{
drawtile2(x * 8, y * 8, tile);
}
else if (tileset == 2)
{
drawtile3(x * 8, y * 8, tile, map.rcol);
}
2020-01-01 21:29:24 +01:00
}
}
}
set_render_target(target);
2020-01-01 21:29:24 +01:00
foregrounddrawn = true;
}
copy_texture(foregroundTexture, NULL, NULL);
2020-01-01 21:29:24 +01:00
}
void Graphics::drawfinalmap(void)
2020-01-01 21:29:24 +01:00
{
if (!foregrounddrawn)
{
SDL_Texture* target = SDL_GetRenderTarget(gameScreen.m_renderer);
set_render_target(foregroundTexture);
set_blendmode(foregroundTexture, SDL_BLENDMODE_BLEND);
clear(0, 0, 0, 0);
if (map.tileset == 0) {
Add and draw one more row to all rooms with roomnames Since translucent roomname backgrounds were introduced in TerryCavanagh/VVVVVV#122, it exposes one glaring flaw with the game that until now has been kept hidden: in rooms with room names, the game cheapens out with the tile data and doesn't have a 30th row, because the room name would hide the missing row. As a result, rooms with room names have 29 rows instead of 30 to fill up the entire screen. And it looks really weird when there's nothing but empty space behind the translucent room name background. To remedy this, I added one row to each room with a room name in the level. First, I had to filter out all the rooms with no room names. However, that's actually all contained in Otherlevel.cpp, the Overworld, which contains 221 rooms (8 of which are the Secret Lab, 6 more of which are the Ship, so 207 are the actual Overworld, right? Wrong, 2 of those Overworld no-roomname rooms are in the Lab, so there are actually 205 Overworld rooms). The remaining level data files all contain rooms with room names. But the process wasn't that easy. I noticed a while ago that each room contains 29 `tmap.push_back()`s, one for each row of the room, and each row is simply a string containing the 40 tiles for that row, concatenated with commas. However, I decided to actually check my intuition by doing a grep on each level file and counting the number of results, for example `grep 'push_back' Labclass.cpp | wc -l`. Whatever number comes out should be divisible by 29. That particular grep on Labclass.cpp returns 1306, which divided by 29 is 45 with a remainder of 1. So what does that mean? Does that mean there's 45 rooms each, and 1 leftover row? Well, not exactly. The extra row comes from the fact that Outer Space has 30 rows instead of 29. Outer Space is the room that comes up when the game finds a room is non-existent, which shouldn't happen with a properly-working game, except in Outside Dimension VVVVVV. In fact, each level file has their own Outer Space, and every single Outer Space also has 30 rooms. So really, this means there are 44 rooms in the Lab and one Outer Space room. (Well, in reality, there are 46 rooms in the Lab, because 2 of them use the Outside tileset but have no room names, so they're stored in Otherlevel.cpp instead.) We find the same result for the Warp Zone. `grep 'push_back' WarpClass.cpp | wc -l` returns 697, which is 24 remainder 1, meaning 23 rooms of 29 rows and 1 room of 30 rows, which corresponds with 23 rooms in the Warp Zone and one Outer Space room. However, Outside Dimension VVVVVV + Tower Hallways and Space Station 1 and 2 are both odd curiosities. Finalclass.cpp contains Outside Dimension VVVVVV, (which is Intermission 1 and 2 and the Final Level), but also the Tower Hallway rooms, i.e. the auxiliary Tower rooms that are not a part of the main tower. Spacestation2.cpp contains both Space Station 1 and 2, so don't be deceived by the name. `grep 'push_back' Finalclass.cpp | wc -l` returns 1597, which is actually 55 remainder 2. So... are there two rooms with 30 rows? Yes, in fact, The Gravitron and Outer Space both contain 30 rows. So there are actually 55 rooms stored in Finalclass.cpp (not including the minitowers Panic Room and The Final Challenge), 54 rooms of actual level data and one Outer Space room, and breaking down the 54 rooms even further, 51 of them are actually in Outside Dimension VVVVVV and 3 of them are Tower Hallways. Of the 51 Outside Dimension VVVVVV rooms, 14 of those are Intermission 1, 4 of them are Intermission 2, and the rest of the 33 rooms are the Final Level (again, not including the minitowers). `grep 'push_back' Spacestation2.cpp | wc -l` returns 2148, which is 74 remainder 2. Are there two rooms with 30 rows again? No; one of those counted 2148 rows is a false-positive, because there's an if-else in Prize for the Reckless that replaces the row with spikes with a row without spikes if you are in a time trial or in No Death Mode. So there's 73 rooms in Space Station 1 and 2, and one Outer Space room. With all this in mind, I decided to duplicate the current last row of each room, the 29th row, to add a 30th row. However, I wasn't going to do this automatically! But neither was I going to write some kludge-y code to parse each nightmare of a level file and duplicate the rows that way. Enter: Vim macros! (Er, well, actually, I use Neovim.) I first did `/push_back`, so that pressing `n` would keep going to the next `push_back` in the file. Then I went to the 29th row of the first room in the file, did a `Yp`, and then started my macro with `qq`. The macro went like this: `30nYp`, which is simply going to the 29th row of the next room over and duplicating it. And that's all there was to it. However, I had to make sure that (1) my cursor was before the `push_back` on the line of the 29th row of the room, and (2) that I didn't skip rooms, both of which were problems I encountered when pressing Ctrl+Z a given invocation of the macro (the Ctrl+Z is just a metaphor, you actually undo by typing `u` in Vim). And also I had to make sure to be careful around the extra lines of `push_back`s in Prize for the Reckless and The Gravitron, and make sure I didn't run past the end of the file and loop back around. Thankfully, all Outer Space rooms are at the end of each file. But first, I had to increase the number of rows drawn in Graphics.cpp by 1 in order to compensate for this, and do the same when reading the tile data in Map.cpp. I had to change fillcontent(), drawmap(), drawfinalmap(), drawtowermap(), and drawtowermap_nobackground(). Funnily enough, the tower functions already used 30 rows, but I guess it's an off-by-one due to the camera scrolling, so they now draw 31 rows each. Then, I went in-game to make sure that the row behind each room name looked fine. I checked EVERY single room with a room name. I turned on invincibility mode and added a temporary line to hardreset() that always turned on game.nocutscenes for a smoother playtesting experience. And to make sure that rooms which have entirely empty bottom rows actually still have 30 rows, instead of having 29 and the game assuming that the 30th row was empty (because that sounds like it could lead to Undefined Behavior), I added this temporary debugging line to the start of mapclass::fillcontent(): printf("(%i,%i) has %i rows\n", game.roomx, game.roomy, (int) tmap.size()); Everywhere I checked - and I made sure to check all rooms - every room had 30 rows and not 29 rows. Unfortunately, some rooms simply couldn't be left alone with their 29th row duplicated and had to be manually edited. This was because the 29th row would contain some edge tiles because the player would be able to walk somewhere on the 28th, 27th, and 26th rows, and if you duplicated said edge tiles behind the room name, it would look bad. Here's a list of rooms whose 30th rows I had to manually edit: - Comms Relay - The Yes Men - Stop and Reflect - They Call Him Flipper - Double-slit Experiment - Square Root - Brought to you by the letter G - The Bernoulli Principle - Purest Unobtainium - I Smell Ozone - Conveying a New Idea - Upstream Downstream - Give Me A V - $eeing Dollar $ign$ - Doing Things The Hard Way - Very Good - Must I Do Everything For You? - Now Stay Close To Me... - ...But Not Too Close - ...Not as I Do - Do Try To Keep Up - Whee Sports - As you like it This is actually a strange case where it looked bad because of the 29th row, instead of the 30th row, and I had to change the 29th row instead of the 30th row to fix it. - Maze With No Entrance - Ascending and Descending - Mind The Gap Same strange case as "As you like it" (it's the 29th row I had to change that was the problem, not the 30th). - 1950 Silverstone Grand V - The Villi People I found that Panic Room and The Final Challenge also looked strange behind the roomname background, but I can't do much about either because towers' tile data wrap around at the top and bottom, and if I added another row to either it would be visible above the room name. I've considered updating the development editors with these new level tiles, but I decided against it as the development editors are already pretty outdated anyway.
2020-02-03 22:24:30 +01:00
for (int j = 0; j < 30; j++) {
for (int i = 0; i < 40; i++) {
if ((map.contents[TILE_IDX(i, j)]) > 0)
drawtile(i * 8, j * 8, map.finalat(i, j));
}
}
}
else if (map.tileset == 1) {
Add and draw one more row to all rooms with roomnames Since translucent roomname backgrounds were introduced in TerryCavanagh/VVVVVV#122, it exposes one glaring flaw with the game that until now has been kept hidden: in rooms with room names, the game cheapens out with the tile data and doesn't have a 30th row, because the room name would hide the missing row. As a result, rooms with room names have 29 rows instead of 30 to fill up the entire screen. And it looks really weird when there's nothing but empty space behind the translucent room name background. To remedy this, I added one row to each room with a room name in the level. First, I had to filter out all the rooms with no room names. However, that's actually all contained in Otherlevel.cpp, the Overworld, which contains 221 rooms (8 of which are the Secret Lab, 6 more of which are the Ship, so 207 are the actual Overworld, right? Wrong, 2 of those Overworld no-roomname rooms are in the Lab, so there are actually 205 Overworld rooms). The remaining level data files all contain rooms with room names. But the process wasn't that easy. I noticed a while ago that each room contains 29 `tmap.push_back()`s, one for each row of the room, and each row is simply a string containing the 40 tiles for that row, concatenated with commas. However, I decided to actually check my intuition by doing a grep on each level file and counting the number of results, for example `grep 'push_back' Labclass.cpp | wc -l`. Whatever number comes out should be divisible by 29. That particular grep on Labclass.cpp returns 1306, which divided by 29 is 45 with a remainder of 1. So what does that mean? Does that mean there's 45 rooms each, and 1 leftover row? Well, not exactly. The extra row comes from the fact that Outer Space has 30 rows instead of 29. Outer Space is the room that comes up when the game finds a room is non-existent, which shouldn't happen with a properly-working game, except in Outside Dimension VVVVVV. In fact, each level file has their own Outer Space, and every single Outer Space also has 30 rooms. So really, this means there are 44 rooms in the Lab and one Outer Space room. (Well, in reality, there are 46 rooms in the Lab, because 2 of them use the Outside tileset but have no room names, so they're stored in Otherlevel.cpp instead.) We find the same result for the Warp Zone. `grep 'push_back' WarpClass.cpp | wc -l` returns 697, which is 24 remainder 1, meaning 23 rooms of 29 rows and 1 room of 30 rows, which corresponds with 23 rooms in the Warp Zone and one Outer Space room. However, Outside Dimension VVVVVV + Tower Hallways and Space Station 1 and 2 are both odd curiosities. Finalclass.cpp contains Outside Dimension VVVVVV, (which is Intermission 1 and 2 and the Final Level), but also the Tower Hallway rooms, i.e. the auxiliary Tower rooms that are not a part of the main tower. Spacestation2.cpp contains both Space Station 1 and 2, so don't be deceived by the name. `grep 'push_back' Finalclass.cpp | wc -l` returns 1597, which is actually 55 remainder 2. So... are there two rooms with 30 rows? Yes, in fact, The Gravitron and Outer Space both contain 30 rows. So there are actually 55 rooms stored in Finalclass.cpp (not including the minitowers Panic Room and The Final Challenge), 54 rooms of actual level data and one Outer Space room, and breaking down the 54 rooms even further, 51 of them are actually in Outside Dimension VVVVVV and 3 of them are Tower Hallways. Of the 51 Outside Dimension VVVVVV rooms, 14 of those are Intermission 1, 4 of them are Intermission 2, and the rest of the 33 rooms are the Final Level (again, not including the minitowers). `grep 'push_back' Spacestation2.cpp | wc -l` returns 2148, which is 74 remainder 2. Are there two rooms with 30 rows again? No; one of those counted 2148 rows is a false-positive, because there's an if-else in Prize for the Reckless that replaces the row with spikes with a row without spikes if you are in a time trial or in No Death Mode. So there's 73 rooms in Space Station 1 and 2, and one Outer Space room. With all this in mind, I decided to duplicate the current last row of each room, the 29th row, to add a 30th row. However, I wasn't going to do this automatically! But neither was I going to write some kludge-y code to parse each nightmare of a level file and duplicate the rows that way. Enter: Vim macros! (Er, well, actually, I use Neovim.) I first did `/push_back`, so that pressing `n` would keep going to the next `push_back` in the file. Then I went to the 29th row of the first room in the file, did a `Yp`, and then started my macro with `qq`. The macro went like this: `30nYp`, which is simply going to the 29th row of the next room over and duplicating it. And that's all there was to it. However, I had to make sure that (1) my cursor was before the `push_back` on the line of the 29th row of the room, and (2) that I didn't skip rooms, both of which were problems I encountered when pressing Ctrl+Z a given invocation of the macro (the Ctrl+Z is just a metaphor, you actually undo by typing `u` in Vim). And also I had to make sure to be careful around the extra lines of `push_back`s in Prize for the Reckless and The Gravitron, and make sure I didn't run past the end of the file and loop back around. Thankfully, all Outer Space rooms are at the end of each file. But first, I had to increase the number of rows drawn in Graphics.cpp by 1 in order to compensate for this, and do the same when reading the tile data in Map.cpp. I had to change fillcontent(), drawmap(), drawfinalmap(), drawtowermap(), and drawtowermap_nobackground(). Funnily enough, the tower functions already used 30 rows, but I guess it's an off-by-one due to the camera scrolling, so they now draw 31 rows each. Then, I went in-game to make sure that the row behind each room name looked fine. I checked EVERY single room with a room name. I turned on invincibility mode and added a temporary line to hardreset() that always turned on game.nocutscenes for a smoother playtesting experience. And to make sure that rooms which have entirely empty bottom rows actually still have 30 rows, instead of having 29 and the game assuming that the 30th row was empty (because that sounds like it could lead to Undefined Behavior), I added this temporary debugging line to the start of mapclass::fillcontent(): printf("(%i,%i) has %i rows\n", game.roomx, game.roomy, (int) tmap.size()); Everywhere I checked - and I made sure to check all rooms - every room had 30 rows and not 29 rows. Unfortunately, some rooms simply couldn't be left alone with their 29th row duplicated and had to be manually edited. This was because the 29th row would contain some edge tiles because the player would be able to walk somewhere on the 28th, 27th, and 26th rows, and if you duplicated said edge tiles behind the room name, it would look bad. Here's a list of rooms whose 30th rows I had to manually edit: - Comms Relay - The Yes Men - Stop and Reflect - They Call Him Flipper - Double-slit Experiment - Square Root - Brought to you by the letter G - The Bernoulli Principle - Purest Unobtainium - I Smell Ozone - Conveying a New Idea - Upstream Downstream - Give Me A V - $eeing Dollar $ign$ - Doing Things The Hard Way - Very Good - Must I Do Everything For You? - Now Stay Close To Me... - ...But Not Too Close - ...Not as I Do - Do Try To Keep Up - Whee Sports - As you like it This is actually a strange case where it looked bad because of the 29th row, instead of the 30th row, and I had to change the 29th row instead of the 30th row to fix it. - Maze With No Entrance - Ascending and Descending - Mind The Gap Same strange case as "As you like it" (it's the 29th row I had to change that was the problem, not the 30th). - 1950 Silverstone Grand V - The Villi People I found that Panic Room and The Final Challenge also looked strange behind the roomname background, but I can't do much about either because towers' tile data wrap around at the top and bottom, and if I added another row to either it would be visible above the room name. I've considered updating the development editors with these new level tiles, but I decided against it as the development editors are already pretty outdated anyway.
2020-02-03 22:24:30 +01:00
for (int j = 0; j < 30; j++) {
for (int i = 0; i < 40; i++) {
if ((map.contents[TILE_IDX(i, j)]) > 0)
drawtile2(i * 8, j * 8, map.finalat(i, j));
}
}
}
set_render_target(target);
foregrounddrawn = true;
}
copy_texture(foregroundTexture, NULL, NULL);
2020-01-01 21:29:24 +01:00
}
void Graphics::drawtowermap(void)
2020-01-01 21:29:24 +01:00
{
const int yoff = lerp(map.oldypos, map.ypos);
Add and draw one more row to all rooms with roomnames Since translucent roomname backgrounds were introduced in TerryCavanagh/VVVVVV#122, it exposes one glaring flaw with the game that until now has been kept hidden: in rooms with room names, the game cheapens out with the tile data and doesn't have a 30th row, because the room name would hide the missing row. As a result, rooms with room names have 29 rows instead of 30 to fill up the entire screen. And it looks really weird when there's nothing but empty space behind the translucent room name background. To remedy this, I added one row to each room with a room name in the level. First, I had to filter out all the rooms with no room names. However, that's actually all contained in Otherlevel.cpp, the Overworld, which contains 221 rooms (8 of which are the Secret Lab, 6 more of which are the Ship, so 207 are the actual Overworld, right? Wrong, 2 of those Overworld no-roomname rooms are in the Lab, so there are actually 205 Overworld rooms). The remaining level data files all contain rooms with room names. But the process wasn't that easy. I noticed a while ago that each room contains 29 `tmap.push_back()`s, one for each row of the room, and each row is simply a string containing the 40 tiles for that row, concatenated with commas. However, I decided to actually check my intuition by doing a grep on each level file and counting the number of results, for example `grep 'push_back' Labclass.cpp | wc -l`. Whatever number comes out should be divisible by 29. That particular grep on Labclass.cpp returns 1306, which divided by 29 is 45 with a remainder of 1. So what does that mean? Does that mean there's 45 rooms each, and 1 leftover row? Well, not exactly. The extra row comes from the fact that Outer Space has 30 rows instead of 29. Outer Space is the room that comes up when the game finds a room is non-existent, which shouldn't happen with a properly-working game, except in Outside Dimension VVVVVV. In fact, each level file has their own Outer Space, and every single Outer Space also has 30 rooms. So really, this means there are 44 rooms in the Lab and one Outer Space room. (Well, in reality, there are 46 rooms in the Lab, because 2 of them use the Outside tileset but have no room names, so they're stored in Otherlevel.cpp instead.) We find the same result for the Warp Zone. `grep 'push_back' WarpClass.cpp | wc -l` returns 697, which is 24 remainder 1, meaning 23 rooms of 29 rows and 1 room of 30 rows, which corresponds with 23 rooms in the Warp Zone and one Outer Space room. However, Outside Dimension VVVVVV + Tower Hallways and Space Station 1 and 2 are both odd curiosities. Finalclass.cpp contains Outside Dimension VVVVVV, (which is Intermission 1 and 2 and the Final Level), but also the Tower Hallway rooms, i.e. the auxiliary Tower rooms that are not a part of the main tower. Spacestation2.cpp contains both Space Station 1 and 2, so don't be deceived by the name. `grep 'push_back' Finalclass.cpp | wc -l` returns 1597, which is actually 55 remainder 2. So... are there two rooms with 30 rows? Yes, in fact, The Gravitron and Outer Space both contain 30 rows. So there are actually 55 rooms stored in Finalclass.cpp (not including the minitowers Panic Room and The Final Challenge), 54 rooms of actual level data and one Outer Space room, and breaking down the 54 rooms even further, 51 of them are actually in Outside Dimension VVVVVV and 3 of them are Tower Hallways. Of the 51 Outside Dimension VVVVVV rooms, 14 of those are Intermission 1, 4 of them are Intermission 2, and the rest of the 33 rooms are the Final Level (again, not including the minitowers). `grep 'push_back' Spacestation2.cpp | wc -l` returns 2148, which is 74 remainder 2. Are there two rooms with 30 rows again? No; one of those counted 2148 rows is a false-positive, because there's an if-else in Prize for the Reckless that replaces the row with spikes with a row without spikes if you are in a time trial or in No Death Mode. So there's 73 rooms in Space Station 1 and 2, and one Outer Space room. With all this in mind, I decided to duplicate the current last row of each room, the 29th row, to add a 30th row. However, I wasn't going to do this automatically! But neither was I going to write some kludge-y code to parse each nightmare of a level file and duplicate the rows that way. Enter: Vim macros! (Er, well, actually, I use Neovim.) I first did `/push_back`, so that pressing `n` would keep going to the next `push_back` in the file. Then I went to the 29th row of the first room in the file, did a `Yp`, and then started my macro with `qq`. The macro went like this: `30nYp`, which is simply going to the 29th row of the next room over and duplicating it. And that's all there was to it. However, I had to make sure that (1) my cursor was before the `push_back` on the line of the 29th row of the room, and (2) that I didn't skip rooms, both of which were problems I encountered when pressing Ctrl+Z a given invocation of the macro (the Ctrl+Z is just a metaphor, you actually undo by typing `u` in Vim). And also I had to make sure to be careful around the extra lines of `push_back`s in Prize for the Reckless and The Gravitron, and make sure I didn't run past the end of the file and loop back around. Thankfully, all Outer Space rooms are at the end of each file. But first, I had to increase the number of rows drawn in Graphics.cpp by 1 in order to compensate for this, and do the same when reading the tile data in Map.cpp. I had to change fillcontent(), drawmap(), drawfinalmap(), drawtowermap(), and drawtowermap_nobackground(). Funnily enough, the tower functions already used 30 rows, but I guess it's an off-by-one due to the camera scrolling, so they now draw 31 rows each. Then, I went in-game to make sure that the row behind each room name looked fine. I checked EVERY single room with a room name. I turned on invincibility mode and added a temporary line to hardreset() that always turned on game.nocutscenes for a smoother playtesting experience. And to make sure that rooms which have entirely empty bottom rows actually still have 30 rows, instead of having 29 and the game assuming that the 30th row was empty (because that sounds like it could lead to Undefined Behavior), I added this temporary debugging line to the start of mapclass::fillcontent(): printf("(%i,%i) has %i rows\n", game.roomx, game.roomy, (int) tmap.size()); Everywhere I checked - and I made sure to check all rooms - every room had 30 rows and not 29 rows. Unfortunately, some rooms simply couldn't be left alone with their 29th row duplicated and had to be manually edited. This was because the 29th row would contain some edge tiles because the player would be able to walk somewhere on the 28th, 27th, and 26th rows, and if you duplicated said edge tiles behind the room name, it would look bad. Here's a list of rooms whose 30th rows I had to manually edit: - Comms Relay - The Yes Men - Stop and Reflect - They Call Him Flipper - Double-slit Experiment - Square Root - Brought to you by the letter G - The Bernoulli Principle - Purest Unobtainium - I Smell Ozone - Conveying a New Idea - Upstream Downstream - Give Me A V - $eeing Dollar $ign$ - Doing Things The Hard Way - Very Good - Must I Do Everything For You? - Now Stay Close To Me... - ...But Not Too Close - ...Not as I Do - Do Try To Keep Up - Whee Sports - As you like it This is actually a strange case where it looked bad because of the 29th row, instead of the 30th row, and I had to change the 29th row instead of the 30th row to fix it. - Maze With No Entrance - Ascending and Descending - Mind The Gap Same strange case as "As you like it" (it's the 29th row I had to change that was the problem, not the 30th). - 1950 Silverstone Grand V - The Villi People I found that Panic Room and The Final Challenge also looked strange behind the roomname background, but I can't do much about either because towers' tile data wrap around at the top and bottom, and if I added another row to either it would be visible above the room name. I've considered updating the development editors with these new level tiles, but I decided against it as the development editors are already pretty outdated anyway.
2020-02-03 22:24:30 +01:00
for (int j = 0; j < 31; j++)
2020-01-01 21:29:24 +01:00
{
for (int i = 0; i < 40; i++)
{
const int temp = map.tower.at(i, j, yoff);
if (temp > 0)
{
drawtile3(i * 8, (j * 8) - (yoff % 8), temp, towerbg.colstate);
}
2020-01-01 21:29:24 +01:00
}
}
}
void Graphics::drawtowerspikes(void)
2020-01-01 21:29:24 +01:00
{
int spikeleveltop = lerp(map.oldspikeleveltop, map.spikeleveltop);
int spikelevelbottom = lerp(map.oldspikelevelbottom, map.spikelevelbottom);
2020-01-01 21:29:24 +01:00
for (int i = 0; i < 40; i++)
{
drawtile3(i * 8, -8+spikeleveltop, 9, towerbg.colstate);
drawtile3(i * 8, 230-spikelevelbottom, 8, towerbg.colstate, 8 - spikelevelbottom);
2020-01-01 21:29:24 +01:00
}
}
void Graphics::drawtowerbackground(const TowerBG& bg_obj)
{
clear();
const int offset = (int) lerp(-bg_obj.bscroll, 0);
const SDL_Rect srcRect = {0, 8 + offset, SCREEN_WIDTH_PIXELS, SCREEN_HEIGHT_PIXELS};
copy_texture(bg_obj.texture, &srcRect, NULL);
}
void Graphics::updatetowerbackground(TowerBG& bg_obj)
2020-01-01 21:29:24 +01:00
{
if (bg_obj.bypos < 0) bg_obj.bypos += 120 * 8;
2020-01-01 21:29:24 +01:00
SDL_Texture* target = SDL_GetRenderTarget(gameScreen.m_renderer);
set_render_target(bg_obj.texture);
if (bg_obj.tdrawback)
2020-01-01 21:29:24 +01:00
{
int off = bg_obj.scrolldir == 0 ? 0 : bg_obj.bscroll;
2020-01-01 21:29:24 +01:00
//Draw the whole thing; needed for every colour cycle!
clear();
for (int j = -1; j < 32; j++)
2020-01-01 21:29:24 +01:00
{
for (int i = 0; i < 40; i++)
{
const int temp = map.tower.backat(i, j, bg_obj.bypos);
drawtile3(i * 8, (j * 8) - (bg_obj.bypos % 8) - off, temp, bg_obj.colstate);
2020-01-01 21:29:24 +01:00
}
}
bg_obj.tdrawback = false;
2020-01-01 21:29:24 +01:00
}
else
{
2023-05-22 21:18:07 +02:00
// just update the bottom
scroll_texture(bg_obj.texture, tempScrollingTexture, 0, -bg_obj.bscroll);
if (bg_obj.scrolldir == 0)
2020-01-01 21:29:24 +01:00
{
for (int i = 0; i < 40; i++)
{
int temp = map.tower.backat(i, -1, bg_obj.bypos);
drawtile3(i * 8, -1 * 8 - (bg_obj.bypos % 8), temp, bg_obj.colstate);
temp = map.tower.backat(i, 0, bg_obj.bypos);
drawtile3(i * 8, -(bg_obj.bypos % 8), temp, bg_obj.colstate);
}
}
else
{
for (int i = 0; i < 40; i++)
{
int temp = map.tower.backat(i, 29, bg_obj.bypos);
drawtile3(i * 8, 29 * 8 - (bg_obj.bypos % 8) - bg_obj.bscroll, temp, bg_obj.colstate);
temp = map.tower.backat(i, 30, bg_obj.bypos);
drawtile3(i * 8, 30 * 8 - (bg_obj.bypos % 8) - bg_obj.bscroll, temp, bg_obj.colstate);
temp = map.tower.backat(i, 31, bg_obj.bypos);
drawtile3(i * 8, 31 * 8 - (bg_obj.bypos % 8) - bg_obj.bscroll, temp, bg_obj.colstate);
temp = map.tower.backat(i, 32, bg_obj.bypos);
drawtile3(i * 8, 32 * 8 - (bg_obj.bypos % 8) - bg_obj.bscroll, temp, bg_obj.colstate);
}
2020-01-01 21:29:24 +01:00
}
}
set_render_target(target);
2020-01-01 21:29:24 +01:00
}
#define GETCOL_RANDOM (game.noflashingmode ? 0.5 : fRandom())
SDL_Color Graphics::getcol( int t )
2020-01-01 21:29:24 +01:00
{
2023-05-22 21:18:07 +02:00
// Setup predefinied colours as per our zany palette
switch(t)
{
2023-05-22 21:18:07 +02:00
// Player Normal
case 0:
2023-05-22 21:18:07 +02:00
return getRGB(160 - help.glow/2 - (int) (GETCOL_RANDOM * 20), 200 - help.glow/2, 220 - help.glow);
// Player Hurt
case 1:
return getRGB(196 - (GETCOL_RANDOM * 64), 10, 10);
2023-05-22 21:18:07 +02:00
// Enemies and stuff
case 2:
return getRGB(225 - (help.glow / 2), 75, 30);
2023-05-22 21:18:07 +02:00
case 3: // Trinket
if (!trinketcolset)
{
trinketr = 200 - (GETCOL_RANDOM * 64);
trinketg = 200 - (GETCOL_RANDOM * 128);
trinketb = 164 + (GETCOL_RANDOM * 60);
trinketcolset = true;
}
return getRGB(trinketr, trinketg, trinketb);
2023-05-22 21:18:07 +02:00
case 4: // Inactive savepoint
{
2023-05-22 21:18:07 +02:00
const int temp = (help.glow / 2) + (int) (GETCOL_RANDOM * 8);
return getRGB(80 + temp, 80 + temp, 80 + temp);
}
2023-05-22 21:18:07 +02:00
case 5: // Active savepoint
return getRGB(164 + (GETCOL_RANDOM * 64), 164 + (GETCOL_RANDOM * 64), 255 - (GETCOL_RANDOM * 64));
2023-05-22 21:18:07 +02:00
case 6: // Enemy : Red
return getRGB(250 - help.glow / 2, 60 - help.glow / 2, 60 - help.glow / 2);
case 7: // Enemy : Green
return getRGB(100 - help.glow / 2 - (int) (GETCOL_RANDOM * 30), 250 - help.glow / 2, 100 - help.glow / 2 - (int) (GETCOL_RANDOM * 30));
case 8: // Enemy : Purple
return getRGB(250 - help.glow / 2, 20, 128 - help.glow / 2 + (int) (GETCOL_RANDOM * 30));
case 9: // Enemy : Yellow
return getRGB(250 - help.glow / 2, 250 - help.glow / 2, 20);
case 10: // Warp point (white)
return getRGB(255 - (GETCOL_RANDOM * 64), 255 - (GETCOL_RANDOM * 64), 255 - (GETCOL_RANDOM * 64));
2023-05-22 21:18:07 +02:00
case 11: // Enemy : Cyan
return getRGB(20, 250 - help.glow / 2, 250 - help.glow / 2);
case 12: // Enemy : Blue
return getRGB(90 - help.glow / 2, 90 - help.glow / 2, 250 - help.glow / 2);
// Crew Members
// green
case 13:
2023-05-22 21:18:07 +02:00
return getRGB(120 - help.glow / 4 - (int) (GETCOL_RANDOM * 20), 220 - help.glow / 4, 120 - help.glow / 4);
// Yellow
case 14:
2023-05-22 21:18:07 +02:00
return getRGB(220 - help.glow / 4 - (int) (GETCOL_RANDOM * 20), 210 - help.glow / 4, 120 - help.glow / 4);
// pink
case 15:
2023-05-22 21:18:07 +02:00
return getRGB(255 - help.glow / 8, 70 - help.glow / 4, 70 - help.glow / 4);
// Blue
case 16:
2023-05-22 21:18:07 +02:00
return getRGB(75, 75, 255 - help.glow / 4 - (int) (GETCOL_RANDOM * 20));
case 17: // Enemy : Orange
return getRGB(250 - help.glow / 2, 130 - help.glow / 2, 20);
case 18: // Enemy : Gray
return getRGB(130 - help.glow / 2, 130 - help.glow / 2, 130 - help.glow / 2);
case 19: // Enemy : Dark gray
return getRGB(60 - help.glow / 8, 60 - help.glow / 8, 60 - help.glow / 8);
// Purple
case 20:
2023-05-22 21:18:07 +02:00
return getRGB(220 - help.glow / 4 - (int) (GETCOL_RANDOM * 20), 120 - help.glow/4, 210 - help.glow/4);
2023-05-22 21:18:07 +02:00
case 21: // Enemy : Light Gray
return getRGB(180 - help.glow/2, 180 - help.glow/2, 180 - help.glow/2);
2023-05-22 21:18:07 +02:00
case 22: // Enemy : Indicator Gray
return getRGB(230 - help.glow/2, 230 - help.glow/2, 230 - help.glow/2);
case 23: // Enemy : Indicator Gray
return getRGB(255 - help.glow / 2 - (int) (GETCOL_RANDOM * 40), 255 - help.glow/2 - (int) (GETCOL_RANDOM * 40), 255 - help.glow/2 - (int) (GETCOL_RANDOM * 40));
2023-05-22 21:18:07 +02:00
// Trophies
// cyan
case 30:
return RGBf(160, 200, 220);
2023-05-22 21:18:07 +02:00
// Purple
case 31:
return RGBf(220, 120, 210);
2023-05-22 21:18:07 +02:00
// Yellow
case 32:
return RGBf(220, 210, 120);
2023-05-22 21:18:07 +02:00
// red
case 33:
return RGBf(255, 70, 70);
2023-05-22 21:18:07 +02:00
// green
case 34:
return RGBf(120, 220, 120);
2023-05-22 21:18:07 +02:00
// Blue
case 35:
return RGBf(75, 75, 255);
2023-05-22 21:18:07 +02:00
// Gold
case 36:
return getRGB(180, 120, 20);
2023-05-22 21:18:07 +02:00
case 37: // Trinket
if (!trinketcolset)
{
trinketr = 200 - (GETCOL_RANDOM * 64);
trinketg = 200 - (GETCOL_RANDOM * 128);
trinketb = 164 + (GETCOL_RANDOM * 60);
trinketcolset = true;
}
return RGBf(trinketr, trinketg, trinketb);
2023-05-22 21:18:07 +02:00
// Silver
case 38:
return RGBf(196, 196, 196);
2023-05-22 21:18:07 +02:00
// Bronze
case 39:
return RGBf(128, 64, 10);
2023-05-22 21:18:07 +02:00
// Awesome
case 40: // Teleporter in action!
{
if (game.noflashingmode)
{
return getRGB(196, 196, 223);
}
const int temp = GETCOL_RANDOM * 150;
if (temp < 33)
{
return RGBf(255 - (GETCOL_RANDOM * 64), 64 + (GETCOL_RANDOM * 64), 64 + (GETCOL_RANDOM * 64));
}
else if (temp < 66)
{
return RGBf(64 + (GETCOL_RANDOM * 64), 255 - (GETCOL_RANDOM * 64), 64 + (GETCOL_RANDOM * 64));
}
else if (temp < 100)
{
return RGBf(64 + (GETCOL_RANDOM * 64), 64 + (GETCOL_RANDOM * 64), 255 - (GETCOL_RANDOM * 64));
}
else
{
return RGBf(164 + (GETCOL_RANDOM * 64), 164 + (GETCOL_RANDOM * 64), 255 - (GETCOL_RANDOM * 64));
}
}
2023-05-22 21:18:07 +02:00
case 100: // Inactive Teleporter
{
const int temp = (help.glow / 2) + (GETCOL_RANDOM * 8);
return getRGB(42 + temp, 42 + temp, 42 + temp);
}
2023-05-22 21:18:07 +02:00
case 101: // Active Teleporter
return getRGB(164 + (GETCOL_RANDOM * 64), 164 + (GETCOL_RANDOM * 64), 255 - (GETCOL_RANDOM * 64));
2023-05-22 21:18:07 +02:00
case 102: // Teleporter in action!
{
if (game.noflashingmode)
{
return getRGB(196, 196, 223);
}
const int temp = GETCOL_RANDOM * 150;
if (temp < 33)
{
return getRGB(255 - (GETCOL_RANDOM * 64), 64 + (GETCOL_RANDOM * 64), 64 + (GETCOL_RANDOM * 64));
}
else if (temp < 66)
{
return getRGB(64 + (GETCOL_RANDOM * 64), 255 - (GETCOL_RANDOM * 64), 64 + (GETCOL_RANDOM * 64));
}
else if (temp < 100)
{
return getRGB(64 + (GETCOL_RANDOM * 64), 64 + (GETCOL_RANDOM * 64), 255 - (GETCOL_RANDOM * 64));
}
else
{
return getRGB(164 + (GETCOL_RANDOM * 64), 164 + (GETCOL_RANDOM * 64), 255 - (GETCOL_RANDOM * 64));
}
}
}
return getRGB(255, 255, 255);
2020-01-01 21:29:24 +01:00
}
#undef GETCOL_RANDOM
2020-01-01 21:29:24 +01:00
void Graphics::menuoffrender(void)
2020-01-01 21:29:24 +01:00
{
if (copy_texture(gameplayTexture, NULL, NULL) != 0)
{
return;
}
const int offset = (int) lerp(oldmenuoffset, menuoffset);
const SDL_Rect offsetRect = {0, offset, SCREEN_WIDTH_PIXELS, SCREEN_HEIGHT_PIXELS};
if (copy_texture(menuTexture, NULL, &offsetRect) != 0)
{
return;
}
}
SDL_Color Graphics::huetilegetcol()
{
if (game.noflashingmode)
{
return getRGB(234, 234, 10);
}
2023-05-22 21:18:07 +02:00
return getRGB(250 - (int) (fRandom() * 32), 250 - (int) (fRandom() * 32), 10);
2020-01-01 21:29:24 +01:00
}
SDL_Color Graphics::bigchunkygetcol(int t)
{
// A seperate index of colours, for simplicity
float random = game.noflashingmode ? 0.5 : fRandom();
switch (t)
{
case 1:
return getRGB(random * 64, 10, 10);
case 2:
2023-05-22 21:18:07 +02:00
return getRGB(160 - help.glow / 2 - (int) (random * 20), 200 - help.glow / 2, 220 - help.glow);
}
const SDL_Color color = {0, 0, 0, 0};
return color;
}
void Graphics::textboxcenterx(void)
2020-01-01 21:29:24 +01:00
{
if (!INBOUNDS_VEC(m, textboxes))
{
vlog_error("textboxcenterx() out-of-bounds!");
return;
}
textboxes[m].should_centerx = true;
2020-01-01 21:29:24 +01:00
}
int Graphics::textboxwidth(void)
2020-01-01 21:29:24 +01:00
{
if (!INBOUNDS_VEC(m, textboxes))
{
vlog_error("textboxwidth() out-of-bounds!");
return 0;
}
return textboxes[m].w;
2020-01-01 21:29:24 +01:00
}
void Graphics::textboxmoveto(int xo)
{
if (!INBOUNDS_VEC(m, textboxes))
{
vlog_error("textboxmoveto() out-of-bounds!");
return;
}
textboxes[m].xp = xo;
2020-01-01 21:29:24 +01:00
}
void Graphics::textboxcentery(void)
2020-01-01 21:29:24 +01:00
{
if (!INBOUNDS_VEC(m, textboxes))
{
vlog_error("textboxcentery() out-of-bounds!");
return;
}
textboxes[m].should_centery = true;
2020-01-01 21:29:24 +01:00
}
int Graphics::textboxwrap(int pad)
{
// TODO: delete this function?
if (!INBOUNDS_VEC(m, textboxes))
{
vlog_error("textboxwrap() out-of-bounds!");
return 16;
}
return textboxes[m].wrap(pad);
}
void Graphics::textboxpad(size_t left_pad, size_t right_pad)
{
if (!INBOUNDS_VEC(m, textboxes))
{
vlog_error("textboxpad() out-of-bounds!");
return;
}
Save special text box state using functions This adds a way to save the text box state of the crew remaining, ACTION prompt, etc. text boxes by just letting there be a function that is called to retranslate the text box when needed. It also adds a way to ignore translating a text box and to leave it alone, in case there's actually no text in the text box, which is the case with Level Complete and Game Complete. Both ways are now in an enum, TextboxTranslate. The former is TEXTTRANSLATE_FUNCTION and the latter is TEXTTRANSLATE_NONE. The existing way of translating text boxes became TEXTTRANSLATE_CUTSCENE, since it's only used for cutscene scripts. Here's a quick guide to the three ways of creating a text box now. - TEXTTRANSLATE_NONE: You must call graphics.textboxoriginalcontextauto() to save the existing text to the original context of the text box, as that will be copied back to the text box after the text of the text box is updated due to not having a translation. - TEXTTRANSLATE_CUTSCENE: Translates the text from cutscenes.xml, and overrides the spacing (padding and text centering). Shouldn't need to be used outside of scriptclass. - TEXTTRANSLATE_FUNCTION: You must pass in a function that takes in a single parameter, a pointer to the textboxclass object to be modified. General advice when retranslating text is to clear the `lines` vector and then push_back the retranslated text. The function is also solely responsible for spacing. In most cases, you will also need to call graphics.textboxapplyposition() or graphics.textboxadjust() afterwards. (Some text boxes shouldn't use graphics.textboxadjust() as they are within the 10-pixel inner border around the screen that textboxclass::adjust tries to push the text box out of.) This commit doesn't fix every text box just yet, though. But it fixes the Level Complete, Game Complete, crew remaining, and ACTION prompt text boxes, for a start.
2024-01-21 05:27:31 +01:00
textboxes[m].spacing.pad_left = left_pad;
textboxes[m].spacing.pad_right = right_pad;
}
void Graphics::textboxpadtowidth(size_t new_w)
{
if (!INBOUNDS_VEC(m, textboxes))
{
vlog_error("textboxpadtowidth() out-of-bounds!");
return;
}
Save special text box state using functions This adds a way to save the text box state of the crew remaining, ACTION prompt, etc. text boxes by just letting there be a function that is called to retranslate the text box when needed. It also adds a way to ignore translating a text box and to leave it alone, in case there's actually no text in the text box, which is the case with Level Complete and Game Complete. Both ways are now in an enum, TextboxTranslate. The former is TEXTTRANSLATE_FUNCTION and the latter is TEXTTRANSLATE_NONE. The existing way of translating text boxes became TEXTTRANSLATE_CUTSCENE, since it's only used for cutscene scripts. Here's a quick guide to the three ways of creating a text box now. - TEXTTRANSLATE_NONE: You must call graphics.textboxoriginalcontextauto() to save the existing text to the original context of the text box, as that will be copied back to the text box after the text of the text box is updated due to not having a translation. - TEXTTRANSLATE_CUTSCENE: Translates the text from cutscenes.xml, and overrides the spacing (padding and text centering). Shouldn't need to be used outside of scriptclass. - TEXTTRANSLATE_FUNCTION: You must pass in a function that takes in a single parameter, a pointer to the textboxclass object to be modified. General advice when retranslating text is to clear the `lines` vector and then push_back the retranslated text. The function is also solely responsible for spacing. In most cases, you will also need to call graphics.textboxapplyposition() or graphics.textboxadjust() afterwards. (Some text boxes shouldn't use graphics.textboxadjust() as they are within the 10-pixel inner border around the screen that textboxclass::adjust tries to push the text box out of.) This commit doesn't fix every text box just yet, though. But it fixes the Level Complete, Game Complete, crew remaining, and ACTION prompt text boxes, for a start.
2024-01-21 05:27:31 +01:00
textboxes[m].spacing.padtowidth = new_w;
}
void Graphics::textboxcentertext(void)
{
if (!INBOUNDS_VEC(m, textboxes))
{
vlog_error("textboxcentertext() out-of-bounds!");
return;
}
Save special text box state using functions This adds a way to save the text box state of the crew remaining, ACTION prompt, etc. text boxes by just letting there be a function that is called to retranslate the text box when needed. It also adds a way to ignore translating a text box and to leave it alone, in case there's actually no text in the text box, which is the case with Level Complete and Game Complete. Both ways are now in an enum, TextboxTranslate. The former is TEXTTRANSLATE_FUNCTION and the latter is TEXTTRANSLATE_NONE. The existing way of translating text boxes became TEXTTRANSLATE_CUTSCENE, since it's only used for cutscene scripts. Here's a quick guide to the three ways of creating a text box now. - TEXTTRANSLATE_NONE: You must call graphics.textboxoriginalcontextauto() to save the existing text to the original context of the text box, as that will be copied back to the text box after the text of the text box is updated due to not having a translation. - TEXTTRANSLATE_CUTSCENE: Translates the text from cutscenes.xml, and overrides the spacing (padding and text centering). Shouldn't need to be used outside of scriptclass. - TEXTTRANSLATE_FUNCTION: You must pass in a function that takes in a single parameter, a pointer to the textboxclass object to be modified. General advice when retranslating text is to clear the `lines` vector and then push_back the retranslated text. The function is also solely responsible for spacing. In most cases, you will also need to call graphics.textboxapplyposition() or graphics.textboxadjust() afterwards. (Some text boxes shouldn't use graphics.textboxadjust() as they are within the 10-pixel inner border around the screen that textboxclass::adjust tries to push the text box out of.) This commit doesn't fix every text box just yet, though. But it fixes the Level Complete, Game Complete, crew remaining, and ACTION prompt text boxes, for a start.
2024-01-21 05:27:31 +01:00
textboxes[m].spacing.centertext = true;
}
void Graphics::textboxprintflags(const uint32_t flags)
{
if (!INBOUNDS_VEC(m, textboxes))
{
vlog_error("textboxprintflags() out-of-bounds!");
return;
}
textboxes[m].print_flags = flags;
textboxes[m].resize();
}
void Graphics::textboxbuttons(void)
{
if (!INBOUNDS_VEC(m, textboxes))
{
vlog_error("textboxbuttons() out-of-bounds!");
return;
}
textboxes[m].fill_buttons = true;
}
void Graphics::textboxcrewmateposition(const TextboxCrewmatePosition* crewmate_position)
{
if (!INBOUNDS_VEC(m, textboxes))
{
vlog_error("textboxcrewmateposition() out-of-bounds!");
return;
}
textboxes[m].crewmate_position = *crewmate_position;
}
void Graphics::textboxoriginalcontext(const TextboxOriginalContext* original_context)
{
if (!INBOUNDS_VEC(m, textboxes))
{
vlog_error("textboxoriginalcontext() out-of-bounds!");
return;
}
textboxes[m].original = *original_context;
}
Save special text box state using functions This adds a way to save the text box state of the crew remaining, ACTION prompt, etc. text boxes by just letting there be a function that is called to retranslate the text box when needed. It also adds a way to ignore translating a text box and to leave it alone, in case there's actually no text in the text box, which is the case with Level Complete and Game Complete. Both ways are now in an enum, TextboxTranslate. The former is TEXTTRANSLATE_FUNCTION and the latter is TEXTTRANSLATE_NONE. The existing way of translating text boxes became TEXTTRANSLATE_CUTSCENE, since it's only used for cutscene scripts. Here's a quick guide to the three ways of creating a text box now. - TEXTTRANSLATE_NONE: You must call graphics.textboxoriginalcontextauto() to save the existing text to the original context of the text box, as that will be copied back to the text box after the text of the text box is updated due to not having a translation. - TEXTTRANSLATE_CUTSCENE: Translates the text from cutscenes.xml, and overrides the spacing (padding and text centering). Shouldn't need to be used outside of scriptclass. - TEXTTRANSLATE_FUNCTION: You must pass in a function that takes in a single parameter, a pointer to the textboxclass object to be modified. General advice when retranslating text is to clear the `lines` vector and then push_back the retranslated text. The function is also solely responsible for spacing. In most cases, you will also need to call graphics.textboxapplyposition() or graphics.textboxadjust() afterwards. (Some text boxes shouldn't use graphics.textboxadjust() as they are within the 10-pixel inner border around the screen that textboxclass::adjust tries to push the text box out of.) This commit doesn't fix every text box just yet, though. But it fixes the Level Complete, Game Complete, crew remaining, and ACTION prompt text boxes, for a start.
2024-01-21 05:27:31 +01:00
void Graphics::textboxoriginalcontextauto(void)
{
if (!INBOUNDS_VEC(m, textboxes))
{
vlog_error("textboxoriginalcontextauto() out-of-bounds!");
return;
}
TextboxOriginalContext context = TextboxOriginalContext();
context.text_case = 1;
context.lines = textboxes[m].lines;
if (script.running)
{
context.script_name = script.scriptname;
}
textboxes[m].original = context;
}
void Graphics::textboxcase(char text_case)
{
if (!INBOUNDS_VEC(m, textboxes))
{
vlog_error("textboxcase() out-of-bounds!");
return;
}
textboxes[m].original.text_case = text_case;
}
Save special text box state using functions This adds a way to save the text box state of the crew remaining, ACTION prompt, etc. text boxes by just letting there be a function that is called to retranslate the text box when needed. It also adds a way to ignore translating a text box and to leave it alone, in case there's actually no text in the text box, which is the case with Level Complete and Game Complete. Both ways are now in an enum, TextboxTranslate. The former is TEXTTRANSLATE_FUNCTION and the latter is TEXTTRANSLATE_NONE. The existing way of translating text boxes became TEXTTRANSLATE_CUTSCENE, since it's only used for cutscene scripts. Here's a quick guide to the three ways of creating a text box now. - TEXTTRANSLATE_NONE: You must call graphics.textboxoriginalcontextauto() to save the existing text to the original context of the text box, as that will be copied back to the text box after the text of the text box is updated due to not having a translation. - TEXTTRANSLATE_CUTSCENE: Translates the text from cutscenes.xml, and overrides the spacing (padding and text centering). Shouldn't need to be used outside of scriptclass. - TEXTTRANSLATE_FUNCTION: You must pass in a function that takes in a single parameter, a pointer to the textboxclass object to be modified. General advice when retranslating text is to clear the `lines` vector and then push_back the retranslated text. The function is also solely responsible for spacing. In most cases, you will also need to call graphics.textboxapplyposition() or graphics.textboxadjust() afterwards. (Some text boxes shouldn't use graphics.textboxadjust() as they are within the 10-pixel inner border around the screen that textboxclass::adjust tries to push the text box out of.) This commit doesn't fix every text box just yet, though. But it fixes the Level Complete, Game Complete, crew remaining, and ACTION prompt text boxes, for a start.
2024-01-21 05:27:31 +01:00
void Graphics::textboxtranslate(const TextboxTranslate translate, const TextboxFunction function)
{
if (!INBOUNDS_VEC(m, textboxes))
{
vlog_error("textboxtranslate() out-of-bounds!");
return;
}
Save special text box state using functions This adds a way to save the text box state of the crew remaining, ACTION prompt, etc. text boxes by just letting there be a function that is called to retranslate the text box when needed. It also adds a way to ignore translating a text box and to leave it alone, in case there's actually no text in the text box, which is the case with Level Complete and Game Complete. Both ways are now in an enum, TextboxTranslate. The former is TEXTTRANSLATE_FUNCTION and the latter is TEXTTRANSLATE_NONE. The existing way of translating text boxes became TEXTTRANSLATE_CUTSCENE, since it's only used for cutscene scripts. Here's a quick guide to the three ways of creating a text box now. - TEXTTRANSLATE_NONE: You must call graphics.textboxoriginalcontextauto() to save the existing text to the original context of the text box, as that will be copied back to the text box after the text of the text box is updated due to not having a translation. - TEXTTRANSLATE_CUTSCENE: Translates the text from cutscenes.xml, and overrides the spacing (padding and text centering). Shouldn't need to be used outside of scriptclass. - TEXTTRANSLATE_FUNCTION: You must pass in a function that takes in a single parameter, a pointer to the textboxclass object to be modified. General advice when retranslating text is to clear the `lines` vector and then push_back the retranslated text. The function is also solely responsible for spacing. In most cases, you will also need to call graphics.textboxapplyposition() or graphics.textboxadjust() afterwards. (Some text boxes shouldn't use graphics.textboxadjust() as they are within the 10-pixel inner border around the screen that textboxclass::adjust tries to push the text box out of.) This commit doesn't fix every text box just yet, though. But it fixes the Level Complete, Game Complete, crew remaining, and ACTION prompt text boxes, for a start.
2024-01-21 05:27:31 +01:00
if (translate == TEXTTRANSLATE_FUNCTION && function == NULL)
{
SDL_assert(0 && "function is NULL!");
return;
}
if (translate != TEXTTRANSLATE_FUNCTION && function != NULL)
{
SDL_assert(0 && "function provided when it won't be used!");
return;
}
Save special text box state using functions This adds a way to save the text box state of the crew remaining, ACTION prompt, etc. text boxes by just letting there be a function that is called to retranslate the text box when needed. It also adds a way to ignore translating a text box and to leave it alone, in case there's actually no text in the text box, which is the case with Level Complete and Game Complete. Both ways are now in an enum, TextboxTranslate. The former is TEXTTRANSLATE_FUNCTION and the latter is TEXTTRANSLATE_NONE. The existing way of translating text boxes became TEXTTRANSLATE_CUTSCENE, since it's only used for cutscene scripts. Here's a quick guide to the three ways of creating a text box now. - TEXTTRANSLATE_NONE: You must call graphics.textboxoriginalcontextauto() to save the existing text to the original context of the text box, as that will be copied back to the text box after the text of the text box is updated due to not having a translation. - TEXTTRANSLATE_CUTSCENE: Translates the text from cutscenes.xml, and overrides the spacing (padding and text centering). Shouldn't need to be used outside of scriptclass. - TEXTTRANSLATE_FUNCTION: You must pass in a function that takes in a single parameter, a pointer to the textboxclass object to be modified. General advice when retranslating text is to clear the `lines` vector and then push_back the retranslated text. The function is also solely responsible for spacing. In most cases, you will also need to call graphics.textboxapplyposition() or graphics.textboxadjust() afterwards. (Some text boxes shouldn't use graphics.textboxadjust() as they are within the 10-pixel inner border around the screen that textboxclass::adjust tries to push the text box out of.) This commit doesn't fix every text box just yet, though. But it fixes the Level Complete, Game Complete, crew remaining, and ACTION prompt text boxes, for a start.
2024-01-21 05:27:31 +01:00
textboxes[m].translate = translate;
textboxes[m].function = function;
textboxes[m].updatetext();
}
static void commsrelay_textbox(textboxclass* THIS)
{
THIS->lines.clear();
if (THIS->original.lines.empty())
{
return;
}
THIS->lines.push_back(loc::gettext(THIS->original.lines[0].c_str()));
THIS->wrap(11);
THIS->resize();
THIS->xp = 224 - THIS->w;
}
void Graphics::textboxcommsrelay(const char* text)
{
// Special treatment for the gamestate textboxes in Comms Relay
if (!INBOUNDS_VEC(m, textboxes))
{
vlog_error("textboxcommsrelay() out-of-bounds!");
return;
}
textboxprintflags(PR_FONT_INTERFACE);
textboxes[m].original.lines.push_back(text);
textboxtranslate(TEXTTRANSLATE_FUNCTION, commsrelay_textbox);
}
2020-01-01 21:29:24 +01:00
int Graphics::crewcolour(const int t)
{
2023-05-22 21:18:07 +02:00
// Given crewmate t, return colour
switch (t)
{
case 0:
return CYAN;
case 1:
return PURPLE;
case 2:
return YELLOW;
case 3:
return RED;
case 4:
return GREEN;
case 5:
return BLUE;
default:
return 0;
}
2020-01-01 21:29:24 +01:00
}
void Graphics::flashlight(void)
2020-01-01 21:29:24 +01:00
{
set_blendmode(SDL_BLENDMODE_NONE);
fill_rect(NULL, 0xBB, 0xBB, 0xBB, 0xBB);
2020-01-01 21:29:24 +01:00
}
void Graphics::screenshake(void)
2020-01-01 21:29:24 +01:00
{
if (gameScreen.badSignalEffect)
{
ApplyFilter(&tempFilterSrc, &tempFilterDest);
}
2020-01-01 21:29:24 +01:00
set_render_target(tempShakeTexture);
set_blendmode(SDL_BLENDMODE_NONE);
clear();
const SDL_Rect shake = {screenshake_x, screenshake_y, SCREEN_WIDTH_PIXELS, SCREEN_HEIGHT_PIXELS};
copy_texture(gameTexture, NULL, &shake);
draw_screenshot_border();
set_render_target(gameTexture);
clear();
// Clear the gameplay texture so blackout() is actually black after a screenshake
if (game.gamestate == GAMEMODE && game.blackout)
{
set_render_target(gameplayTexture);
clear();
}
set_render_target(NULL);
set_blendmode(SDL_BLENDMODE_NONE);
clear();
copy_texture(tempShakeTexture, NULL, NULL, 0, NULL, flipmode ? SDL_FLIP_VERTICAL : SDL_FLIP_NONE);
2020-01-01 21:29:24 +01:00
}
void Graphics::updatescreenshake(void)
{
screenshake_x = static_cast<Sint32>((fRandom() * 7) - 4);
screenshake_y = static_cast<Sint32>((fRandom() * 7) - 4);
}
void Graphics::render(void)
2020-01-01 21:29:24 +01:00
{
draw_screenshot_border();
if (gameScreen.badSignalEffect)
{
ApplyFilter(&tempFilterSrc, &tempFilterDest);
}
set_render_target(NULL);
set_blendmode(SDL_BLENDMODE_NONE);
clear();
copy_texture(gameTexture, NULL, NULL, 0, NULL, flipmode ? SDL_FLIP_VERTICAL : SDL_FLIP_NONE);
2020-01-01 21:29:24 +01:00
}
void Graphics::renderwithscreeneffects(void)
{
if (game.flashlight > 0 && !game.noflashingmode)
{
flashlight();
}
if (game.screenshake > 0 && !game.noflashingmode)
{
screenshake();
}
else
{
render();
}
}
void Graphics::renderfixedpre(void)
{
if (game.screenshake > 0)
{
updatescreenshake();
}
if (gameScreen.badSignalEffect)
{
UpdateFilter();
}
}
void Graphics::renderfixedpost(void)
{
/* Screen effects timers */
if (game.flashlight > 0)
{
--game.flashlight;
}
if (game.screenshake > 0)
{
--game.screenshake;
}
game.old_screenshot_border_timer = game.screenshot_border_timer;
if (game.screenshot_border_timer > 0)
{
game.screenshot_border_timer -= 15;
}
}
void Graphics::draw_screenshot_border(void)
{
const int border_alpha = lerp(game.old_screenshot_border_timer, game.screenshot_border_timer);
if (border_alpha <= 100)
{
return;
}
int width = 0;
int height = 0;
int result = query_texture(gameTexture, NULL, NULL, &width, &height);
if (result != 0)
{
return;
}
const SDL_Rect rect_inner = {1, 1, width - 2, height - 2};
if (game.screenshot_saved_success)
{
set_color(196, 196, 20, border_alpha);
}
else
{
set_color(196, 20, 20, border_alpha);
}
set_blendmode(SDL_BLENDMODE_BLEND);
draw_rect(NULL);
draw_rect(&rect_inner);
set_blendmode(SDL_BLENDMODE_NONE);
}
void Graphics::drawtele(int x, int y, int t, const SDL_Color color)
2020-01-01 21:29:24 +01:00
{
SDL_Rect telerect;
setRect(telerect, x, y, tele_rect.w, tele_rect.h);
draw_grid_tile(grphx.im_teleporter, 0, x, y, tele_rect.w, tele_rect.h, 16, 16, 16);
2020-01-01 21:29:24 +01:00
if (t > 9) t = 8;
if (t < 1) t = 1;
2020-01-01 21:29:24 +01:00
draw_grid_tile(grphx.im_teleporter, t, x, y, tele_rect.w, tele_rect.h, color);
2020-01-01 21:29:24 +01:00
}
SDL_Color Graphics::getRGBA(const Uint8 r, const Uint8 g, const Uint8 b, const Uint8 a)
2020-01-01 21:29:24 +01:00
{
const SDL_Color color = {r, g, b, a};
return color;
2020-01-01 21:29:24 +01:00
}
SDL_Color Graphics::getRGB(const Uint8 r, const Uint8 g, const Uint8 b)
2020-01-01 21:29:24 +01:00
{
const SDL_Color color = {r, g, b, 255};
return color;
2020-01-01 21:29:24 +01:00
}
SDL_Color Graphics::RGBf(int r, int g, int b)
2020-01-01 21:29:24 +01:00
{
r = (r + 128) / 3;
g = (g + 128) / 3;
b = (b + 128) / 3;
const SDL_Color color = {(Uint8) r, (Uint8) g, (Uint8) b, 255};
return color;
2020-01-01 21:29:24 +01:00
}
bool Graphics::onscreen(int t)
{
return (t >= -40 && t <= 280);
2020-01-01 21:29:24 +01:00
}
Use levelDirError for graphics errors too This will actually do several things: (1) Make the tile size checks apply to the appropriate graphics files once again. (2) Make the game print a fallback error message if the error message hasn't been set on the levelDirError error screen. (3) Use levelDirError for graphics errors too. (4) Make the error message for tile size checks failing specify both width and height, not just a square dimension. (5) Make the error messages mentioned above translatable. It turns out that (1) didn't happen after #923 was merged, since #923 removed needing to process a tilesheet into a vector of surfaces for all graphics files except sprites.png and flipsprites.png. Thus, the game ended up only checking the correct tile sizes for those files only. In the process of fixing this, I also got rid of the PROCESS_TILESHEET macros and turned them into two different functions: One to make the array, and one to check the tile size of the tilesheet. I also did (2) just in case FILESYSTEM_levelDirHasError() returns false even though we know we have an error. And (3) is needed so things are unified and we have one user-facing error message system when users load levels. To facilitate this, I removed the title string, since it's really not needed. Unfortunately, (1) doesn't apply to font.png again, but that's because of the new font stuff and I'm not sure what Dav999 has in store for error checking. But that's also why I did (4), because it looks like tile sizes in font.png files can be different (i.e. non-square).
2023-05-18 02:00:33 +02:00
bool Graphics::checktexturesize(
const char* filename, SDL_Texture* texture,
const int tilewidth, const int tileheight
) {
int texturewidth;
int textureheight;
if (query_texture(texture, NULL, NULL, &texturewidth, &textureheight) != 0)
{
/* Just give it the benefit of the doubt. */
vlog_warn(
"Assuming the dimensions of %s are exact multiples of %i by %i!",
filename, tilewidth, tileheight
);
return true;
}
const bool valid = texturewidth % tilewidth == 0 && textureheight % tileheight == 0;
if (!valid)
{
FILESYSTEM_setLevelDirError(
loc::gettext("{filename} dimensions not exact multiples of {width} by {height}!"),
"filename:str, width:int, height:int",
filename, tilewidth, tileheight
);
return false;
}
return true;
}
static void make_array(
SDL_Surface** tilesheet,
std::vector<SDL_Surface*>& vector,
const int tile_square
) {
int j;
for (j = 0; j < (*tilesheet)->h / tile_square; j++)
{
int i;
for (i = 0; i < (*tilesheet)->w / tile_square; i++)
{
SDL_Surface* temp = GetSubSurface(
*tilesheet,
i * tile_square, j * tile_square,
tile_square, tile_square
);
vector.push_back(temp);
}
}
}
bool Graphics::reloadresources(void)
{
grphx.destroy();
grphx.init();
Use levelDirError for graphics errors too This will actually do several things: (1) Make the tile size checks apply to the appropriate graphics files once again. (2) Make the game print a fallback error message if the error message hasn't been set on the levelDirError error screen. (3) Use levelDirError for graphics errors too. (4) Make the error message for tile size checks failing specify both width and height, not just a square dimension. (5) Make the error messages mentioned above translatable. It turns out that (1) didn't happen after #923 was merged, since #923 removed needing to process a tilesheet into a vector of surfaces for all graphics files except sprites.png and flipsprites.png. Thus, the game ended up only checking the correct tile sizes for those files only. In the process of fixing this, I also got rid of the PROCESS_TILESHEET macros and turned them into two different functions: One to make the array, and one to check the tile size of the tilesheet. I also did (2) just in case FILESYSTEM_levelDirHasError() returns false even though we know we have an error. And (3) is needed so things are unified and we have one user-facing error message system when users load levels. To facilitate this, I removed the title string, since it's really not needed. Unfortunately, (1) doesn't apply to font.png again, but that's because of the new font stuff and I'm not sure what Dav999 has in store for error checking. But that's also why I did (4), because it looks like tile sizes in font.png files can be different (i.e. non-square).
2023-05-18 02:00:33 +02:00
MAYBE_FAIL(checktexturesize("tiles.png", grphx.im_tiles, 8, 8));
MAYBE_FAIL(checktexturesize("tiles2.png", grphx.im_tiles2, 8, 8));
MAYBE_FAIL(checktexturesize("tiles3.png", grphx.im_tiles3, 8, 8));
MAYBE_FAIL(checktexturesize("entcolours.png", grphx.im_entcolours, 8, 8));
MAYBE_FAIL(checktexturesize("sprites.png", grphx.im_sprites, 32, 32));
MAYBE_FAIL(checktexturesize("flipsprites.png", grphx.im_flipsprites, 32, 32));
MAYBE_FAIL(checktexturesize("teleporter.png", grphx.im_teleporter, 96, 96));
destroy();
Use levelDirError for graphics errors too This will actually do several things: (1) Make the tile size checks apply to the appropriate graphics files once again. (2) Make the game print a fallback error message if the error message hasn't been set on the levelDirError error screen. (3) Use levelDirError for graphics errors too. (4) Make the error message for tile size checks failing specify both width and height, not just a square dimension. (5) Make the error messages mentioned above translatable. It turns out that (1) didn't happen after #923 was merged, since #923 removed needing to process a tilesheet into a vector of surfaces for all graphics files except sprites.png and flipsprites.png. Thus, the game ended up only checking the correct tile sizes for those files only. In the process of fixing this, I also got rid of the PROCESS_TILESHEET macros and turned them into two different functions: One to make the array, and one to check the tile size of the tilesheet. I also did (2) just in case FILESYSTEM_levelDirHasError() returns false even though we know we have an error. And (3) is needed so things are unified and we have one user-facing error message system when users load levels. To facilitate this, I removed the title string, since it's really not needed. Unfortunately, (1) doesn't apply to font.png again, but that's because of the new font stuff and I'm not sure what Dav999 has in store for error checking. But that's also why I did (4), because it looks like tile sizes in font.png files can be different (i.e. non-square).
2023-05-18 02:00:33 +02:00
make_array(&grphx.im_sprites_surf, sprites_surf, 32);
make_array(&grphx.im_flipsprites_surf, flipsprites_surf, 32);
images[IMAGE_LEVELCOMPLETE] = grphx.im_image0;
images[IMAGE_MINIMAP] = grphx.im_image1;
images[IMAGE_COVERED] = grphx.im_image2;
images[IMAGE_ELEPHANT] = grphx.im_image3;
images[IMAGE_GAMECOMPLETE] = grphx.im_image4;
images[IMAGE_FLIPLEVELCOMPLETE] = grphx.im_image5;
images[IMAGE_FLIPGAMECOMPLETE] = grphx.im_image6;
images[IMAGE_SITE] = grphx.im_image7;
images[IMAGE_SITE2] = grphx.im_image8;
images[IMAGE_SITE3] = grphx.im_image9;
images[IMAGE_ENDING] = grphx.im_image10;
images[IMAGE_SITE4] = grphx.im_image11;
images[IMAGE_CUSTOMMINIMAP] = grphx.im_image12;
gameScreen.LoadIcon();
music.destroy();
music.init();
tiles1_mounted = FILESYSTEM_isAssetMounted("graphics/tiles.png");
tiles2_mounted = FILESYSTEM_isAssetMounted("graphics/tiles2.png");
minimap_mounted = FILESYSTEM_isAssetMounted("graphics/minimap.png");
gamecomplete_mounted = FILESYSTEM_isAssetMounted("graphics/gamecomplete.png");
levelcomplete_mounted = FILESYSTEM_isAssetMounted("graphics/levelcomplete.png");
flipgamecomplete_mounted = FILESYSTEM_isAssetMounted("graphics/flipgamecomplete.png");
fliplevelcomplete_mounted = FILESYSTEM_isAssetMounted("graphics/fliplevelcomplete.png");
return true;
fail:
return false;
}
SDL_Color Graphics::crewcolourreal(int t)
{
switch (t)
{
case 0:
return col_crewcyan;
case 1:
return col_crewpurple;
case 2:
return col_crewyellow;
case 3:
return col_crewred;
case 4:
return col_crewgreen;
case 5:
return col_crewblue;
}
return col_crewcyan;
}
void Graphics::render_roomname(uint32_t font_flag, const char* roomname, int r, int g, int b)
{
int font_height = font::height(font_flag);
if (font_height <= 8)
{
footerrect.h = font_height + 2;
}
else
{
footerrect.h = font_height + 1;
}
footerrect.y = 240 - footerrect.h;
set_blendmode(SDL_BLENDMODE_BLEND);
fill_rect(&footerrect, getRGBA(0, 0, 0, translucentroomname ? 127 : 255));
font::print(font_flag | PR_CEN | PR_BOR | PR_CJK_LOW, -1, footerrect.y+1, roomname, r, g, b);
set_blendmode(SDL_BLENDMODE_NONE);
}
void Graphics::print_roomtext(int x, const int y, const char* text, const bool rtl)
{
uint32_t flags = PR_FONT_LEVEL | PR_CJK_LOW;
if (rtl)
{
flags |= PR_RIGHT;
x += 8; // the size of a tile, not font width!
}
font::print(flags, x, y, text, 196, 196, 255 - help.glow);
}