This involves a couple of dialog boxes ("Congratulations!" and the
jukebox) which were supposed to have blank lines, but they weren't
in the translation.
LodePNG always loads PNG in big-endian RGBA format. For this reason,
when loading PNG files VVVVVV was specifying the format as ABGR and the
conversion would then be performed by SDL. However, then running on
big-endian machines, this conversion should not be performed at all, and
the surface format should then be set to RGBA.
We recently had a user come in the VVVVVV Discord not knowing that,
after running CMake, you then need to compile the game in a separate
step. This clarifies the instructions.
This fixes a segmentation fault caused by an out-of-bounds indexing
caused by an attempt to unwordwrap a string that starts with two
newlines.
The problem here is that in the branch of the function
string_unwordwrap() where `consecutive_newlines == 1`, the function does
not check that the string `result` isn't empty before attempting to
index `result.size()-1`. If `result` is empty, then `result.size()` is
0, and `result.size()-1` becomes -1, and indexing a string at position
-1 is always undefined behavior.
Funnily enough, a similar indexing happens just a few lines down, but
this time, there is a check to make sure that the string isn't empty
first. I'm unsure of how Dav999 forgot that check a few lines earlier.
This situation can happen in practice, with custom level localizations.
I made a level with a filename of testloc.vvvvvv and created a file at
lang/fr/levels/testloc/custom_cutscenes.xml with the following content:
<?xml version="1.0" encoding="UTF-8"?>
<cutscenes>
<cutscene id="test" explanation="">
<dialogue speaker="cyan" english="This is text..." translation="blarg"/>
</cutscene>
</cutscenes>
Then I switched to French, created a script named `test`, and created a
text box that started with two newlines (so in total, the text box must
be at least 3 lines in length). Running the script triggers the segfault
when the text box is created. (Well, technically, on my machine, it
triggers an assertion fail in libstdc++ and aborts, but that's basically
the same thing.)
To fix this while still preserving the exact amount of newlines, if
`result` is empty, we add a newline instead of attempting to index the
string.
These strings had been replaced over time and the original versions
marked ***OUTDATED*** to allow for the original wordings to be reused
by the translators who had only translated the original ones.
(See lang/README-programmers.txt.)
Now, these strings have all been updated in every language, so it's
time to clean them up!
These were not in the English or any other language files. They should
be though, so that they can be translated and generally kept track of.
These aren't urgent either, since we have proxy strings that are used
if these are untranslated.
This hasn't been done since before we got some deliveries for 2.4,
so there are a few languages which added apostrophes as ' instead of
' in the XML (which is not wrong, but it gives diff noise whenever
there's a sync since VVVVVV writes them back as '...)
Also, we never synced "[Press {button} to toggle gameplay]" across
language files (now two strings with unfreeze/freeze), but that was
also a pretty last-minute string as far as I remember. Arabic did have
it because that language was added after the string was added, so it
got copied from English. I don't think this one is that urgent to
translate into every language for 2.4.1 since it's pretty well hidden
for most people, and it's surrounded by things that have to be English,
so it's as if it's supposed to be like that. Let's just include these
with whatever the next batch of strings is.
This is both easier for translators ("toggle" can be an annoying word)
and is useful in general because you can tell if gameplay is frozen
without having to have anything in the room that should normally be
moving but isn't.
I didn't follow the rule in lang/README-programmers.txt to keep the
original string around as ***OUTDATED*** in this case, since I know
only Arabic has it translated - we can just tell the Arabic translators
on Discord that this string was replaced.
It's kind of a bummer that L/R don't actually do anything... we should add ZL/ZR support at some point.
Also note that GameCube binds X to 'back' rather than B, this will be fixed by SDL_ActionSet for 2.5.
The original Chinese font (which we call "the Indienova font") was
received from the Chinese translators directly, and didn't come with
any license or copyright information other than that it was made by
Indienova. Questions have now been raised about the actual origin of
the characters in the font, and while we do have confirmation from the
translators that we're probably in the clear, they did suggest another
font for us to use, which we're switching to to be sure.
Some background information: the ideal font would probably be Ark Pixel
(https://github.com/TakWolf/ark-pixel-font/), but this font is not
finished yet. Therefore, the creators of Ark Pixel have made a font
that can be used as a placeholder to use in the meantime, Fusion Pixel
(https://github.com/TakWolf/fusion-pixel-font), which combines some
other fonts together in order to get full coverage. This is the font
we're now switching to.
It's not _that_ simple though - the ASCII part of Fusion Pixel is kinda
bad for us using it as a monospaced font. Normally I just replace the
ASCII set by the fullwidth characters, but in this font they were
almost entirely the same. So I instead picked the fullwidth characters
from Galmuri 12px, which is one of the "fusioned" fonts. Interestingly,
we happen to also use the 10px version of this as our Korean font, and
I like these Latin letters, so yay.
I also made the call to split the Chinese font into separate variants
for Simplified and Traditional Chinese. I was aware of the problem with
the Han Unification, but the Traditional Chinese translator said the
Indienova font also contains all the Traditional Chinese characters,
and they proofread the translation, so it was probably fine. Apparently
the difference between Simplified and Traditional Chinese variants of
the same characters are not that big, and it's acceptable. But
Fusion Pixel gives us separate versions of the font for Simplified and
Traditional Chinese, so this is a chance to get it right. Just kidding,
Fusion Pixel's Traditional variant switches out many characters that
were shared between Simplified and Traditional Chinese to Japanese
variants which are noticeably different. So it would be better to keep
using the SC font for TC, just like the Indienova font is SC only.
However: Ark Pixel does have a version with correct characters for
Traditional Chinese! So for the TC version of our font, I just took all
Chinese characters from the TC version of Ark Pixel where available.
That way, all characters I checked have changed to TC variants
correctly.
This removes every single translation of a wordy number that just
replaces it with the numeral.
This is because the documentation in README-translators.txt specifically
says
It's also possible to leave the translations for all the numbers
empty. In that case, numeric forms will always be used.
However, the translators for Japanese, Korean, and European Portuguese
clearly either didn't read this, or forgot to do so.
There is a very good reason to leave them alone if you want numerals;
namely that if you fill them in, you are prone to making errors. Like,
say, Japanese translating "Twelve" as "23", which is exactly what
happened. By blanking every translation, that error is fixed.
This reverts the following commits:
- 29f05c41b1
- f1bf1f683c
- a7b22919ae
- 2ed1aac67d
Recently, text images were changed to fade in with textboxes, where
before they previously appeared after the fade. This created a charming
effect where the images would appear to "load in" once the textbox
finishes fading in. This behavior really complements the retro
aesthetic the game is going for. Changing it to a fade is a needless
change in direction, as it was not a bug in the first place and looked
good already.
Additionally, custom levels have used text images (levelcomplete and
gamecomplete) in creative ways by replacing them with something else
to show as 'foreground' or as a cutscene image. Changing text images
to fade has unintended consequences for levels that have utilized
them in this fashion.
13d6b2d64c adds a check where you can't
type a pipe in script/terminal input fields anymore. It does this
completely incorrectly, checking if certain variables are set instead
of checking what's actually trying to be done.
This commit fixes that, simplifying the check a lot in the process.
This disables typing the pipe character in the data fields of terminals
and script boxes. Care has been taken to make sure that it's still
possible to type pipes in room text.
This is because pipes are the line separator in the big XML tag that
stores every single script line, and thus a script name with pipes would
end up being split up after the level file has been saved and loaded
again.
While a7b22919ae makes text sprites
modulate their RGB values, text images continued using alpha,
despite alpha blending not even being enabled, so the initial
commit didn't work right either.
This is quite a last-minute thing that was almost getting called off by
me discovering a critical segfault just now in testing this (whew) but
this shouldn't hurt.
This reverts commit a806b072bd.
It causes an instant segfault if there's no entities or if you're not
editing terminals/script boxes or something, whatever it's not
crucial as a last-minute fix.
No Death Mode is intended to be unlocked by getting at least S-rank in
at least 4 time trials. Before 2.3, completing a time trial put you at
the main menu, so you would always be notified of having unlocked No
Death Mode once you went to the play menu again. But since 2.3,
completing a time trial puts you back at the Time Trial selection
screen, which isn't the play menu, so you would need to back all the way
out first in order to get the notification. And since you don't actually
unlock No Death Mode until you see the notification, this would be
required to be able to play No Death Mode.
To fix this, I decided to do something a bit kludge-y and just re-use
the code to check and unlock No Death Mode when the player presses
ACTION on the Time Trial complete screen (and there's also another path
by pressing Escape). At least I put it in a function, so it's not a pure
copy-paste, although it might as well be. I don't have time to think of
a proper solution, but it would probably involve disentangling unlock
notifications from Menu::play, for starters. But that's for later.
This disables typing the pipe character in the data fields of terminals
and script boxes. Care has been taken to make sure that it's still
possible to type pipes in room text.
This is because pipes are the line separator in the big XML tag that
stores every single script line, and thus a script name with pipes would
end up being split up after the level file has been saved and loaded
again.
This makes it so that the boolean to draw mode indicator text is false
if there aren't any modes active.
Otherwise, when loading in, the in-game timer would only come in after a
few seconds instead of appearing when the fade-in finishes.
This fixes a bug where pressing Escape in the following menus would not
play Presenting VVVVVV (the title screen music) and would instead leave
you with silence: Game Complete, Time Trial complete, Game Over, and No
Death Mode complete.
This makes it so that only inputs between 1 and 255 inclusive will be
accepted. Otherwise, the command has no effect.
This is because the text case is stored as one byte in a string, and a
value of zero would be the null terminator.
We also want to minimize potential weirdness with integer wrapping if we
accept inputs from outside those bounds. While the textcase variable as
used throughout the codebase is plain unqualified `char` (which, unlike
other integers, exists in a quantum superposition of being signed and
unsigned depending on compiler, machine, and various other stuff) and so
there still might be issues there, we definitely don't want anything
higher than 255.
If the language is RTL, then the left and right menu navigation keys
should be reversed, because the menu layout goes from right to left.
This is to be consistent with the other menus in the game. The editor is
just a special case so it was overlooked.
There was an inconsistency where W and S couldn't be used in place of
the Up and Down arrow keys, but this has been fixed.
This only applies where W and S otherwise are not bound to anything
else. E.g. not the main editor (where W changes the warp direction and S
saves the level) and not the script editor (where W and S can be typed
inside a script), but the script list is fine.
Currently, it's a bit jarring that text box overlays (which are text box
images, e.g. Level Complete and Game Complete, and sprites, e.g. the
crewmates) will suddenly appear when their text box has fully faded in
and suddenly disappear once it starts fading out.
This makes it so that text box overlays will be faded in and out
smoothly along with the rest of the text box fading in and out.
Transparent text boxes are not affected, as they do not fade in and out
at all. Thus, text box overlays in transparent text boxes will still
suddenly appear and disappear as usual.
The number "one" in Dutch is "één" (silly, I know :P). Capital letters
can have accents, but there's an exception where for this specific
word, the first accent is much more often left off than not. So I'm
now using wordy2 as the uppercase variants of all the numbers, and
using that instead of the |upper flag.
I forgot that the position of the activity zone can vary based on
setactivityposition() in custom levels. So account for that.
Additionally, if the activity zone prompt is far down enough, then we
don't need to move the mode text at all.
Dav999 notified me that if multiple screenshots are taken in the same
second, the second screenshot has `_2` appended to it and so on. We do
the same here by storing the current timestamp and a counter.
This doesn't prevent overwriting files if you have system time that
changes, or have multiple instances of VVVVVV running at the same time,
but my position on those cases is as follows: Don't do that.
This makes the mode indicator text be visible even if there is an
activity zone prompt on screen, by making it so that it gets moved if an
activity prompt is being rendered.
This is to make sure that it's visible no matter what, even if e.g. a
custom level starts the player on an activity zone.
Trophy text can overlap with the timer. How bad it is depends on the
localization but in English some text definitely overlaps.
Simple fix is to disable rendering the timer if we are rendering any
trophy text.
These are functions used in other files that are not on the
GraphicsResources class but are implemented inside the
GraphicsResources.cpp file. For some reason they were never put in the
GraphicsResources.h file until now, even though it's a perfectly good
header file to put them in.
Filenames are timestamped now, down to the second. If you take multiple
screenshots in the same second, then the last one will overwrite the
others. This seems to be how other screenshot programs operate so I
don't think it matters if you can't take more than one per second.
Additionally, 1x screenshots (320x240) will go in the 1x/ subdirectory,
and 2x screenshots (640x480) will go in the 2x/ subdirectory.
Originally, I was thinking of adding a notification text that you took a
screenshot, but this is better because it is language-agnostic and it
doesn't contribute to potential UI clutter/clashing.
It flashes yellow if the screenshot successfully saved, and red if it
didn't.
The plan is to have Steam screenshots always be 2x, but in the VVVVVV
screenshots directory (for F6 keybind) save both 1x and 2x.
Again, just for now, the 2x screenshot is being saved to a temporary
location for testing and will get proper timestamps later.
One problem with internal screenshot capture is that we rely on SDL's
render subsystem to flip the screen in Flip Mode, while leaving our
actual screen untouched. Since we source the screenshot from the screen
and not what SDL renders, we need to flip the screenshot ourselves when
saving an internal capture.
To do this, we need to support 24-bit colors in DrawPixel() and
ReadPixel(). Luckily, this isn't too hard to do. A 24-bit color is just
a tuple of three bytes, and we just need to do a small amount of bitwise
math to pack/unpack them to a single integer for SDL_GetRGB() and
SDL_MapRGB().
Using the Steamworks API, we can hook the screenshot function and listen
for a screenshot request callback to send in our own screenshot. This
applies the screenshot improvements to Steam screenshots as well.
Doing this requires adding some C wrapper functions, as our interface
with the Steam API is only conducted through C.
"But people already have screenshot tools", you might protest. The
rationale is simple: If you play with any video setting other than 1x
windowed (no stretching and no letterbox), then your screenshot will be
too big if you want the internal resolution of 320x240, and downscaling
will be an inconvenience.
The point is to make screenshots based off of internal resolution so
they are always pixel perfect and ideally never have to be altered once
taken.
I've added the keybind of F6 to do this.
Right now it saves to a temporary test location with the same filename;
future commits will save to properly-timestamped filenames.
This is mostly so people making levels in an RTL language have a more
pleasant and logical experience. If roomtext is placed in a level set
to RTL, it will get p1=1, which makes that roomtext right-aligned.
Because, imagine for English you click to place roomtext, and the text
runs left of where you clicked, which wouldn't be logical.
Since it's an entity-bound property, switching RTL on and off either in
the editor or via a script does not affect existing entities.
This remaps the keybind to reload language files from F12 to F8.
This is because the F12 keybind conflicts with the default Steam keybind
to take a Steam screenshot.
I chose F8 because it is next to another keybind that reloads stuff, F9
(which reloads assets in the editor).
Fixes#1089.
While working on adding a screenshots keybind, I encountered a link
error with these functions. Wrapping them in `extern "C"` fixed it. It's
most likely due to the fact that they were `extern "C"` in the header,
but not in the `.cpp` file. They should be both `extern "C"`'d
regardless.
If you load in to gameplay with invincibility mode, glitchrunner mode,
Flip Mode, or slowdown enabled, then there will be text displayed on
screen for a few seconds that says so.
This is to serve as a useful reminder. A common pitfall with using
invincibility is forgetting to turn it off when you don't want it
anymore. What usually happens is that players forget that they have it
on until they encounter a hazard. Now, they can realize it as soon as
they load in.
See #1091.
If text is set to be centered, but is so long that it starts running
offscreen on both sides, the print function instead makes the text
start no further left than the left border of the screen (x=0).
This is because text running offscreen at the end only is more readable
and looks less sloppy than running offscreen at both sides.
For RTL, the opposite applies, so it now also works oppositely for RTL
prints, where centered strings will only run offscreen on the left side
of the screen.
Spaces on the left and right would end up on the other side in RTL,
which made the "You have rescued a crewmate!" text overlap with the
crewmate sprite, and makes the [C[C[C[C[Captain!] dialogs have spaces
on the left instead of on the right. So, best thing is to just swap
the directions so that they match.
They're invisible in font::print(), but they were still considered
characters with widths in the width function. This change made the
levels screen look better in RTL too - I was wondering why the level
options were too far left.
If you copy-paste a newline character where it's not interpreted, such
as in a level title, the print function wouldn't treat it any special.
font::print_wrap() would, but that's not used here.
However, now that bidi is involved, the newline is passed straight to
SheenBidi which interprets it as a new line (which would need a new
SBLine to be created, or maybe even a new SBParagraph if there's two).
All while we're still treating it as a single line. This means the text
would just stop being displayed after the first newline. This is now
fixed by treating all newlines as spaces.
This has a lot of reading-orientation stuff on it like "Key: value",
so easiest is to just flip the whole design of the screen rather than
trying to flip individual strings.
I forgot to add the PR_RTL_XFLIP flag to these menu options, so they
were always left-aligned, no matter what.
What actually took me a bit to figure out was how to make the level
completion stars work regardless of the contents of the title - the
stars should always be to the left of the title in an LTR language, and
always to the right of the title in an RTL language. Level titles can
contain bidi characters regardless of the level's rtl flag being set,
so I just let bidi handle all the level menu options, with some control
characters to make sure everything always appears in the correct order.
Stuff like centertext="1" and padtowidth="264" in cutscene translations
looked wrong in RTL mode, both with Arabic and English text. For Arabic
text, I could easily fix the problem by not counting the number of
codepoints (and assuming they all have the same glyph width), but by
instead taking the width of the string as reported for the font, and
dividing it by the glyph width. This leaves English text still looking
weird in RTL mode. But this shouldn't be a problem either: the Arabic
translations will probably be in Arabic (where the problem doesn't
happen), and I can get English text to show up fine by wrapping it in
U+2066 LEFT-TO-RIGHT ISOLATE and U+2069 POP DIRECTIONAL ISOLATE. So it
looks like an inherent quirk of bidi, that translators familiar with
bidi can easily grasp and fix.
This is main-game only functionality, so it shouldn't break existing
custom levels. We should just make sure textboxes in other languages
aren't broken, but from my testing, it's completely fine - in fact, it
should've improved if it was broken.
Instead of just up/down, you can also control menus with left/right.
Which is illogical in Arabic... No big deal, I imagined this code
to become much worse than it did. (And action sets is probably gonna
refactor the whole thing anyway)
Okay, the "Font:" thing needed some local code after all, because both
the interface font as well as the level font are used there. But it's
good enough - all the other places can just use the flag.
Notably, I also used this for the menus, since the existing ones are
kinda LTR-oriented, and it's something that we don't *really* have to
do, but I think it shows we care!
This lets you mirror the X axis specifically in RTL languages, so the
left border is 320 and the right border is 0, and invert the meaning of
PR_LEFT (0) and PR_RIGHT. Most of the time this is not necessary,
it's just for stuff where a label is followed by a different print,
like "Font: " followed by the font name, time trial time displays, etc
With the <font> tag (which doesn't indicate RTL-ness as explained),
we've had a setfont(font) scripting command. Now we have an <rtl>
tag, so we need a setrtl(on/off) command too to control that.
Again, the RTL property controls whether textboxes will be
right-aligned, and that kind of stuff. It can't be font-bound, since
Space Station supports Hebrew characters and we want to be able to
support, say, a Hebrew translation or Hebrew levels in the future
without having to make a dedicated (or duplicated) font for it.
Therefore it's a property of both the language pack as well as custom
levels - like custom levels already had a <font> tag, they now also
have an <rtl> tag that sets this property.
Right now, we'll have to hardcode it so the menu option for the Arabic
font sets the <rtl> property to 1, and all the other options set it to
0. But it's future-proof in that we can later decide to split the
option for Space Station into an LTR option and an RTL option (so both
"english/..." and "עברית" would select Space Station, but one sets the
RTL property to 0 and the other sets it to 1).
This now returns true if any of the characters in the text belong to
the Arabic or Hebrew alphabet, or are one of the Unicode directional
formatting characters. This is just so the bidi machinery doesn't have
to run 100% of the time for 100% of the languages. I will also make it
so the Arabic language pack, as well as custom levels, have an RTL
attribute that always enables bidi (and does things like
right-alignment in textboxes and other design-flipping)
I'm now using SheenBidi to reorder RTL and bidirectional text properly
at text rendering time! For Arabic this is still missing reshaping, but
everything's looking really promising now!
The code changes are really non-invasive. The changes to Font.cpp are
absolutely minimal:
1305+ if (bidi_should_transform(text))
1306+ {
1307+ text = bidi_transform(text);
1308+ }
There's now a FontBidi.cpp, which implements these two functions,
notably bidi_transform(), which takes a UTF-8 encoded string and
returns another UTF-8 encoded string that has bidi reorderings and
reshapings applied.
In that function, SheenBidi gives us information about where in the
input string runs start and end, and on a basic level, all we need to
do there is to concatenate the parts together in the order that we're
given them, and to reverse the RTL runs (recognizable by odd levels).
As this is a proof-of-concept, bidi_should_transform() still always
returns true, applying the bidi algorithm to all languages and all
strings. I'm thinking of enabling bidi only when the language/font
metadata enables RTL (which could be for the interface or for a custom
level), or outside of that, at least when RTL characters are detected
(such as Arabic or Hebrew Unicode blocks).