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).
I'm going to give it a shot to use this for bidi text support, it looks
like it's a pretty lightweight, compatible and low-dependency library
which is definitely a plus. We'll still need to do reshaping ourselves,
but that's the easy part compared to bidi.
This makes it so that the main CI workflow will only trigger if a change
is made to a code file in desktop_version/ (as the CI is only for
desktop_version/), or if the CI file itself is changed.
The CI workflow for Android will only trigger if Android-specific code
_could have_ changed. This includes all code that is definitely
Android-specific (e.g. Java files), but also C/C++ files that have
__ANDROID__ ifdefs.
Unfortunately, it's not possible to reuse the same list of paths across
two different event trigger types[1]. So we have to copy-paste here.
[1]: https://github.com/orgs/community/discussions/37645
I had added 1px spaces in some Japanese strings with buttons in them,
to avoid the button glyphs touching the rest of the text. However, the
Japanese translator later ended up putting full spaces in, not noticing
the hair spaces. So now the space was 1 pixel wider than it should've
been, and it's better to remove them.
This fixes a bug where you could still drag an entity around with the
debugger inactive if you were holding the entity while disabling the
debugger with Y. Furthermore, you couldn't even drop the entity even if
you wanted to.
There is a clash between the timer text and the "Survive for 60
seconds!" text. It's minor in English but it can be worse in other
languages (e.g. Polish).
So make the timer go away when that text is onscreen.
The "[Press {button} to return to editor]" and the "TIME:" text
overlapped, which resulted in an ugly clash.
To fix this, make the return editor text take priority over the timer
text. This involves a minor refactor to first calculate whether or not
we should draw the return editor text before we check if we should draw
the timer text.
Some languages have different spellings of wordy numbers based on the
gender of the things they're counting (uno crewmate versus una trinket)
or what a number's role is in the sentence (e.g. twenta out of twentu).
We've always had the idea we couldn't support such complex differences
though, because the game can't be adapted to know what gender each
object will have and what word classes might exist in other languages,
so translators would in those cases just have to forgo the wordy
numbers and just let the game use "20 out of 20".
A solution we came up semi-recently though (after all translations were
finished except for Arabic), was to allow the translator to define
however many classes of wordy numbers they need, and fill them all out.
This would not need the game to be *adapted* for every language's
specific grammar and word genders/classes. Instead, the translator
would just choose their correct self-defined class at the time they use
`wordy` in the VFormat placeholder. Something like
{n|wordy|class=feminine}, or {n|wordy_feminine}.
So this would benefit several languages, but we came up with the
solution a little late for all languages to benefit from it. The Arabic
translators asked for two separate classes of wordy numbers though, so
my plan is to first just have a second list of wordy numbers
(translation2 in numbers.xml), which can be accessed by passing the
`wordy2` flag to VFormat, instead of `wordy`.
Once 2.4 is released, we can take our time to do it properly. This
would involve the ability for translators to define however many
classes they need, to name them what they want, and this name would
then be useable in VFormat placeholders. We can convert all existing
translations to have one class defined by default, such as "wordy", or
"translation" depending on implementation, but there's not so much
concern for maintaining backwards compatibility here, so we can do a
mass-switchover for all language files. That said, it wouldn't be too
hard to add a special case for "translation" being "wordy" either.
We can then ask translators if they would like to change anything with
the new system in place.
For now, we can use this system for Arabic, maybe Spanish since there
were complaints about uno/una, and *maybe* Dutch (it has a thing where
the number "one" is often capitalized differently, but it's not
mandatory per se)
For some reason, the default behavior of SDL and/or Windows(?) (I only
tested this on Windows) seems to result in the fact that if any SDL app
doesn't account for it, there is no way for Japanese and Chinese
speakers to know what they're typing in.
How IMEs are supposed to work is that you can type words as sort of
WIP versions, and then select out of a list of candidates what the
final result should be. The app may display the WIP text and tell the
IME where the text field is so that the IME's menu can be displayed
around it. But if the app doesn't say where the text field is, then the
candidate list can also be displayed at the corner of the screen, which
is done in Minecraft.
By default, however, SDL apps don't get a candidate list at all, which
means you're basically flying blind as to what you're typing in, and
you would have to basically open notepad and copy-paste everything from
there - unless I'm missing something.
This commit sets the SDL_HINT_IME_SHOW_UI hint (added in SDL 2.0.18
apparently), so that the candidate list is at least shown in the corner.
We can probably deal with positioning and uncommitted text later.
The TAB bind is used in both roomname translator mode and the level
debugger. To fix this, the TAB keybind will prioritize roomname
translator mode, unless the debugger text is enabled (with the Y
keybind), in which case the debugger takes priority. Additionally, the
roomname translator text will not render when the debugger text is
shown.
Fixes#1094.