Fixes#1242.
Turns out it was a really simple fix - the X positions were good, but
the Y positions were always at the top of the screen regardless of the
height of the textbox. Now they're vertically centered respective to
the speaker.
This fixes our problem with Valgrind reporting a jump based on an
uninitialized value; see https://github.com/Tehreer/SheenBidi/issues/19
Just to be sure nothing unexpected happens, I tested that this doesn't
cause any changes in behavior by outputting bidi-transformed versions
of all strings in strings.xml to a file for both our Arabic and Persian
localizations before and after the update, and confirming that the
files are the same.
This includes translations that were missing translations, with varying
extent between different languages, for the following things:
- "X mode is enabled" in-game warnings
- "Press {button} to freeze/unfreeze gameplay" for the level debugger
- Some credits strings for the post-2.4.0 extra Spanish options, the
PT_BR proofread, and Persian
- The recent gamepad menu changes (#1229)
Furthermore:
- "TAB" (used in the level debugger string) is now a separate string
instead of being hardcoded, because some languages needed it
translated
- Added missing arrows to Arabic/Persian font (needed for the gamepad
menu, and also a translator menu actually)
Seems like the build root can change and CMake rightfully gets confused at the conflicting paths, so let's just use the minutes to do this from scratch.
- Update actions/checkout to v4
- Add build caching for macOS and Linux jobs
- Implement concurrency control to cancel redundant runs
- Restrict GITHUB_TOKEN permissions for security
These changes improve CI performance, stability, and security.
When writing the initial stretch mode code, the existence of HiDPI
displays completely slipped my mind -- or at least I didn't realize
that they'd be a problem.
We implement scaling modes ourselves, so transforming the mouse
coordinates to our 320x240 viewport is done manually. Unfortunately,
that code did not take into account HiDPI scaling whatsoever, meaning
that all of the math which assumes the window size and the renderer
size is wrong.
To fix this, we use `SDL_GetWindowSizeInPixels` and `SDL_GetWindow` to
find the scaling factor, and then apply that to the mouse coordinates
to get the mouse coordinates in the pixel-space instead, and then we do
all of our normal logic after.
Due to our usage of `SDL_GetWindowSizeInPixels`, this bumps the minimum
SDL version to 2.26.0.
Closes#1235.
A SDL_GameControllerButton below 0 is out of array bounds, so that
should trip the assert and return GLYPH_UNKNOWN just like when the
value is too high.
This makes the following improvements to the gamepad bindings menu:
- The menu now shows a hint that you can press a button while any of
the bind options are selected (or that you can navigate away from
those options)
- Instead of button presses immediately setting a binding, they now
ask for confirmation: press the same button a second time to confirm
- You can now remove a binding, the same way you add it (this has the
same type of confirmation)
- This menu used to be inconsistent with pretty much every other menu
in the game by showing a permanent title and description for the menu
itself ("Game Pad", "Change controller options.") rather than showing
a title and description for the currently selected option.
This inconsistency is now fixed.
The Persian localizers noticed an issue where the translation for
"Dimension VVVVVV, 12:34:56" would become "VVVVVV, 12:34:56 noisnemiD",
rather than "12:34:56 ,VVVVVV noisnemiD" as expected. This was fixed
for Persian but the same issue also affected Arabic, so I added a
RIGHT-TO-LEFT MARK (U+200F) to fix it there as well.
This adds "Persian" to all the languages, and brings the Persian files
up-to-date (mainly removing outdated strings and adding some late 2.4
stuff that we'll contact all translators for soon)
Every now and then, the game crashes for me because of a division by
zero, due to the rect returned by `Graphics::get_stretch_info`. I don't
fully know how the width and height get set to 0, but this should
protect against it.
As I always use integer scaling, my guess is that
`Screen::GetScreenSize` (which later calls `SDL_GetRendererOutputSize`)
returns 0 sometimes, and the code trusts that -- but I know that
windows and things can be finicky, so the clamp is probably a good
idea.
When `SDL_RenderReadPixels` fails for some reason, the game tries to
free the temporary source surface it creates. Unfortunately, it never
sets it to `NULL` after, so the next time the game tries to render the
filter, it'll try to work with a memory region that was already freed.
To fix this, I just replaced `SDL_FreeSurface(*src);` with
`VVV_freefunc(SDL_FreeSurface, *src);` which is a helper macro which
sets the pointer to NULL after freeing.
Now, there's a new issue -- since the temporary buffer is now NULL,
next frame we'll try to remake it! So I've introduced a static bool
which disables the filter entirely if `SDL_RenderReadPixels` fails.
Without this, it'd create and destroy a surface every frame, which
could lead to slowdown. (Not as slow as the filter when it DOES work,
but still...)
I also added a line which frees the second temporary surface... it's
weird that was missing in the first place, but I think reimplementing
analogue mode was one of the last things I did for the renderer
rewrite anyways.
Resolves#1223.
PR #1226 tweaks both the crew and the stats screens. When there's no
trinkets in the level, it removes the trinket count from the STATS
screen in the menu. This, however, is missing a `custommode` check,
meaning that the main game is ALSO missing the trinket count.
This PR fixes that oversight.
There's a problem in #1224 where it breaks spawning custom activity
zones. After a bit of confusion after this was reported, I realized
that I removed the fallback from `getcrewman` and changed the return
value to `-1` if the entity isn't found, to avoid returning the wrong
entity (entity 0, aka probably the player).
Unfortunately, it seems like a ton of levels (including my older ones)
rely on this behavior.
Creating custom activity zones is a long process which uses a bunch of
unintended behaviour, which includes targeting a crewmate with color
35. With the change I mentioned earlier, the `getcrewman` function
would return `-1` instead, which was out of bounds of the entity array,
so the game avoided spawning the activity zone at all. The prior
behaviour of falling back to entity 0 (most likely the player) would
spawn the activity zone around the player instead.
Nowadays, I try to spawn a crewmate with color 35 anyways so I can
control where the box spawns (instead of on the player always), however
most people don't (and haven't) so reverting this change seems best for
now.
If we wanted to reintroduce the `-1` fallback in the future, things
that call `getcrewman` would have to check for `-1` and use `0`
instead, but that would require a lot more testing and studying where
it's used, and I'd rather squash this bug quickly and worry about
cleanliness later.
Entity colors are just integers. Their colour ID gets passed through a
big switch, returning different RGB values depending on the colour ID
(and can also get affected by randomness or other game state.)
But because of this, there's a bunch of random numbers floating around,
with no actual description on what they are other than comments which,
while most of the time are accurate, only exist in the switch.
To fix this, this commit adds a new enum which labels every colour.
While we can't use it as a type (as we need to allow colours outside of
what are defined, in case people want a "pure white", and scripting can
set any colour ID they want), colours have to stay as `int`.
This PR adds a new XML property for the player's colour. It is 0 by
default, but you can change it to any colour ID. For example, making
the player use the trinket color is `<PlayerColour>3</PlayerColour>`.
This is mostly a quality-of-life addition, as the player's colour is
always 0 unless changed by scripts. A lot of levels which use different
player colours use an intro script which both changes the player's
colour and sets their respawn colour, which works great for finished,
completed levels, but makes playtesting a little more annoying as they
will spawn in as the wrong colour. Adding a level property for the
default player colour fixes this annoyance.
Additionally, this changes the behavior of `restoreplayercolour`. This
command used to set the player's colour to 0 (ignoring the respawn
colour, so when Viridian would die, they would revert to the respawn
colour). Now, it sets both Viridian's colour AND the respawn colour to
what was present in the level file. This way, you can temporarily
change the player colour using the script commands, and then use
`restoreplayercolour` to revert back to what the player colour
normally is.
The start point colour has also changed, to show the player colour
instead of always colour 0.
Like most changes like this, a way to change this in-editor does not
yet exist, but is planned for the future.
Turns out there was a little miscommunication with the translator -
they said the option needed to say "Ślōnsko" but were only talking
about correcting the first word, where I thought the whole thing needed
to be replaced.
If you push a button to set a controller binding, you may either hear
one Viridian squeak, two Viridian squeaks (a louder one), or Viridian
doesn't stop squeaking until you let go of the button. While you hear
the continuous squeaking, your save file is also repeatedly saved.
There are two small bugs at play here:
- the squeak is actually played in two different places at the same
time (both in titleinput() whenever a button is pressed, and in
updatebuttonmappings() when a mapping is succesfully changed)
- titleinput() doesn't register that a button is held down and applies
the button (and saves to file) every frame for as long as the button
is held
This commit fixes both these issues. Now a single button press always
causes one squeak, and only if the bindings actually changed. Your save
file is also no longer saved repeatedly from holding down the button.
In an effort to remove magic numbers, I've given every entity type a
name. Hopefully I didn't miss anywhere.
Also, add `createentity` case 100 for backwards compatibility.
Co-authored-by: NyakoFox <nyakowofox@gmail.com>
Co-authored-by: Dav999 <dav999.tolp@gmail.com>
This argument forces the textbox position, meaning it won't be moved
to be inside of the bounds of the screen (nor have the 10 pixel padding
on each side.)
This replaces all instances of unlocking all rooms on the map with calls
to map.fullmap(), for consistency.
This also fixes two comments that got swapped around in startgamemode().
I don't know how that happened.
[skip ci]
Some discussion on the Discord server resulted in this change. It's a
quality-of-life improvement where, if the game is in slowdown mode, it
will return to 100% speed for the duration of the death animation.
The reasoning is obvious. There is nothing to do during the death
animation, so making it take longer during slowdown is just an annoyance
to the player, almost a penalty for them using an accessibility option.
This is the same reason why slowdown no longer applies in menus, etc.
This makes it so that it is possible to obtain the Master of the
Universe trophy/achievement, usually unlocked by beating No Death Mode,
outside of NDM.
There are several conditions that need to be met:
1. The game needs to be started from a new game and cannot be from
loading a save.
2. Accessibility modes (invincibility and slowdown) must never be
enabled.
If either condition is violated, then the boolean that keeps track of
NDM eligibility will be set to false.
Currently, you can change platform speed, but not enemy speed, which is
always hardcoded to be 4. This commit fixes that, by adding the
"enemyv" property, which is an offset to the speed of 4. Since it
defaults to 0, older levels are not broken by this change.
Just extending the selection background left by one pixel so there's
not one pixel of black background to the left of a selection that
starts at the beginning of the text, and so some characters being
selected show up better (particularly where there's a long vertical bar
at the first pixel). We shouldn't be overlapping any part of the
previous character, since every character normally has a pixel of
spacing on the right.
Decided to implement it anyway since the broken behavior (selection
length always being 0, at least on Windows) may get fixed later in SDL,
so let's do it right in one go.
This shows the uncommitted text in a box in the bottom left corner.
This doesn't show the selection (defined by the start and length fields
in the event) yet, but this is already much better than it was on its
own, and I don't know how urgent the selection is since it's broken on
Windows anyway.
When inputting uncommitted text from an IME, this is now stored in a
std::string imebuffer, just like keybuffer. It also enables extended
editing events, so text longer than what fits in the standard editing
event is also supported. This commit does not yet display the text
onscreen.
This fixes a regression from 2.3. Consider the following diagram:
CC
X CC
<<<<
"C" indicates one tile of a checkpoint entity, "X" indicates a spike
tile, and "<" indicates one tile of a conveyor entity that has the
default speed (4 pixels per frame) going leftwards.
Now consider if the player were to touch the checkpoint and die. In 2.2,
they would be able to escape from the spike by holding right. But in
2.3, they would not be able to, and would die from the spike
continuously with no escape.
This happens because in 2.2, the player would spawn a couple pixels off
the surface, and this gained them an extra frame of movement to move
away from the conveyor. 2.3 places the player directly on the ground,
moving them one frame earlier and thus forcing them to their doom.
Now consider the following diagram:
CC
X CC
<<<<
The difference with the previous diagram is that this time, the spike is
one tile closer. This time, there is no escape in 2.2 and you will
always die no matter what.
By the way, both diagrams have the same behavior if the conveyor is
rightwards and if everything is flipped upside-down. Thankfully, it
doesn't seem to be direction-dependent.
The reason 2.3 lowered the player onto the surface was for consistency
(see PR #502), and I don't want to undo that. So I think the best
solution here is to re-add the extra frame of control by conveyors only
moving the player after lifeseq has advanced enough.
This fixes a bug where fast-forward wouldn't work in 30-FPS-only mode.
This is because the 30-FPS-only code has a hardcoded check for the
number 34, as in 34 milliseconds must pass before the next frame can
advance. This is why slowdown still worked, because slowdown means
you're waiting longer than 34 ms anyways, but fast-forward tries to wait
for only 1 ms, which wouldn't work if the 34 limit was still enforced.
So instead, swap out the 34 with game.get_timestep() and this will be
fixed.
Fixes#1185.
This fixes an issue where the CentOS CI kept failing because it couldn't
find the generated InterimVersion output file.
It seems like using the BYPRODUCTS statement in add_custom_target
didn't work because BYPRODUCTS was only added in CMake 3.2, so then
add_custom_target never ran, which is obviously a problem.
The solution is to use add_custom_command instead, and to solve the
problem that the interim version needs to be regenerated every time no
matter what (which is what BYPRODUCTS was supposed to do) we just add a
dummy output instead.
Previously, the interim version indicators (commit, date, and branch)
would go away on development builds if git didn't exist. And if it did
exist but provided blank output for whatever reason, that was almost
exactly the same as not having them at all (save for the window title
saying "VVVVVV []" which can be easy to overlook). This was bad because
it complicates troubleshooting when you potentially have an unofficial
or in-development build since those get distributed around or compiled
by some people frequently.
Now, there will always be an interim version indicators unless the game
is compiled with -DOFFICIAL_BUILD=ON. And if the indicators are blank
for any reason, they will just be replaced with placeholder defaults so
they will still show up.
GCC on CentOS will default to C90, it seems. This means it needs C99
explicitly specified for C-Hashmap and FAudio, or it will fail on them
using C99 features (variable declaration in a `for`-loop and the
`restrict` keyword, respectively).
Due to a confluence of weird factors, it turns out that PhysFS is
compiling with implicit function definitions due to function definitions
that get hidden with -std=c99, but not with -std=gnu99 (or the default
GCC value of -std=gnu17).
Also, due to a recent GCC update (GCC 14), implicit function
declarations are actually prohibited with -std=c99 as the C99 standard
proscribes.
This meant that people started getting build errors in PhysFS code on
default settings, which wasn't ideal.
To fix this, we will make our -std= flags apply only to VVVVVV source
files. In CMake 2.8.12, this can be done with
set_source_files_properties. Additionally the flags to disable
exceptions and RTTI are scoped down too.
Thanks to leo60228 for helping debug and solve this issue.
Fixes#1167.
A minor gripe, but one thing I didn't notice with commit
b4579d88d3 is that it now results in two
dialogs if data.zip is missing: The first being the "data.zip is
missing" dialog, and the second is the generic "Unable to initialize
filesystem" dialog.
So just bail early if data.zip can't be found, it's going to take the
error path in main() and also bail regardless.
`/EHsc` does not actually disable exceptions on MSVC, it only makes the
compiler assume that `extern "C"` functions never throw C++ exceptions.
We had a discussion on Discord about actually disabling exceptions, and
from research, that requires defining `_HAS_EXCEPTIONS=0`, but it's
unsupported and undocumented so we deemed the benefits not worth it.
Thus, we will stay with `/EHsc`. But the comment still has to be
updated.
[skip ci]
lang/README-programmers.txt accidentally lists the name of the
font::print function twice, when the second one should've been
font::print_wrap instead. Oops.
[skip ci]
Commit 53d725f78a, intended to fix an
overzealous commit, was itself overzealous. This is because it applied
to all entities when it should only apply to entity-emitting entities.
To fix this, `entityclonefix` needs to no-op if the entity is not an
entity emitter.
Fixes#1176.
Commit 4f881b9e26 fixed a duplication bug
where enemy movement types 10 and 12 would keep duplicating itself on
every frame if it was spawned outside of the rooms they were supposed to
be used in the main game. The downside was that this was an overzealous
fix and unintentionally broke some cases that were working before.
As brought to my attention by Ally, you can no longer place an edentity
with a `p1` of 10 or 12 (translating to movement type 10 or 12) in the
proper rooms and have it spawn perfectly working entities (that don't
clone on themselves every frame), whereas you could in 2.2. This is
considered a regression from 2.3.
So the problem here is that the reason the two emitter entities were so
dangerous outside their respective rooms is because the entity they
spawned (`createentity` entry 1) checked if it was in the correct rooms,
and if so, it would call `setenemy`, and `setenemy` would set the
`behave` attribute (movement type) correctly, and so the new entity
would have a different `behave` that wouldn't be the exact same `behave`
as the previous one, so it wouldn't be a duplicate emitter entity.
The previous `entityclonefix` worked okay for entry 1, because it would
only be run if the room checks failed and `setenemy` wasn't called, but
it broke a previously-working case for entry 56, because it was always
run for entry 56.
So the best way to check if we have a dangerous entity is not by seeing
if it is still `behave` 10 or 12 at the end of entity creation - because
10 or 12 could be harmless under the right conditions - but by checking
if the right conditions were satisfied, and if not, then neutralize the
entity.
I considered making the emitter entities work everywhere - which would
be simpler - but I didn't want to go too far and add a new feature,
especially in a minor release.
This fixes a minor issue where if you had a zip in the levels list, but
then removed it, it would still show up in the levels list after
reloading it (if you also had a .vvvvvv file named the same as in the
zip) even though it shouldn't.
Thankfully, this didn't lead to a memory leak or duplicate zip mounts or
anything like that, because PhysFS ignores mounting a zip if it's
already mounted.
This also didn't result in a level entry from a zip persisting after
removal after reloading the levels list, because the entry would be gone
due to the .vvvvvv file not being found.
The intention of the `-console` argument was to enable seeing console
output on Windows without having to use workarounds. However, this
didn't actually work for arguments like `-addresses` and `-version`,
because the program would exit first before it could get the chance to
create the console.
The other issue is that the console closes too quickly before output can
be read by the user. So to fix that, we must hold it open and let the
user close it when they want to by waiting for an enter press from
STDIN.
This fixes a regression from 2.4 where the foreground wouldn't update
after using the G keybind to go to a room, requiring the user to touch a
tile to update the rendering.
This fixes a bug report from Elomavi that you could still softlock from
warping to ship and incrementing the gamestate by pressing ACTION, which
is diverging behavior from how it was in 2.3. Warping to ship and
incrementing by pressing ACTION is useful behavior for a couple niche
speedrun categories.
I had already fixed this earlier by ignoring state locking if
glitchrunner 2.2 or 2.0 was enabled, but softlocks could still happen
because having glitchrunner mode off still enabled you to increment the
gamestate when otherwise unintended. Softlocks shouldn't happen.
But without removing state locking entirely, I've chosen a middle ground
where it will only be disabled if you press ACTION. That signifies
intent that you still want to perform state incrementing glitches even
with glitchrunner mode off (but in the future it could be considered a
2.3/2.4 glitch that could be patched and made re-enable-able). That way,
casual players can't interrupt the warp to ship by accident (unless they
accidentally press ACTION) while softlocks will be removed.
For localization, the "Thanks for playing!" text was split into two
lines, when it was originally one line. Unfortunately, it was not
updated to account for Flip Mode, so in Flip Mode, it looked like
"playing! Thanks for".
This has been fixed.
If there was a scaling mode value (serialized in the XML as <stretch>
for legacy reasons) that was not 0 or 1 or 2, then the rectangle with
the stretch information would not be initialized by get_stretch_info,
which would lead to a crash, either from dividing by zero (most likely)
or from reading an uninitialized value.
To fix this, when reading <stretch>, normalize it to a sane default if
the value is otherwise bogus. And for good measure, an assertion is
added in get_stretch_info() if the value is still somehow bogus.
Fixes#1155.
This is just a small visual fix to an inconsistency with textbox
colors in simplified scripting. The `reply` command is meant to be
used for the player, and always correctly positions it above the
player, while the `say` command may be used to generate a cyan textbox
that's positioned above a cyan non-player crewmate. However, the color
for both textboxes is always `cyan`, so the `reply` command doesn't use
the (normally identical) `player` color even though all its other
behavior (squeak, position) does. Now that customized textbox colors
were added in 2.4 (#910), it's a shame that this distinction isn't
made between `cyan` and `player`, so this change addresses that (before
we're stuck with levels that change `cyan` but not `player`).
After some discussion about the previous commit, the usecase of
managing tons of basedirs and locking files in the filesystem might
mean it gets annoying to have the language screen show up again
whenever a new language is added, for a small group of people. The
solution to get the best of both worlds is to only re-ask for the
language in the default basedir. This means barely anyone will miss
their language having been newly added (especially since barely anyone
will use any custom basedirs, let alone ONLY custom ones).
Now that two new variants of Spanish have been added, it would be
a shame that many players from Latin-America/Argentina may stay on
Castilian or English because they don't realize the new versions
were added for them. So now, if you've set your language in 2.4.0,
the language screen will show up once more in 2.4.1. This is done by
simply incrementing the lang_set flag to 2 - so that if it's 0 or 1,
your language setting is considered to be possibly outdated.
This shouldn't inconvenience players who don't need to select a new
language - their existing language will still be pre-selected, so they
can just hit ACTION once.
Terry confirms he did the same thing with Dicey Dungeons and says
it's a good idea (and that nobody minds).
This fixes the possibility of the "resize to nearest" graphics option
resizing the game window to be bigger than the resolution of the user's
desktop monitor.
To fix this, just subtract multiples of 320x240 until the chosen
multiple is smaller than the dimensions of the desktop.
Discord user Dzhake discovered this issue.
All of them were changed except for the one in meta.xml. I think it's
safe to assume this is correct, because everywhere else, the same
"Oprime {button} para" pattern always became "Pulsa {button} para" too.
For future PRs, it'll be very nice to have full control over how VVVVVV
gets drawn to the window. This means we can use the entire window size
for things like touch input, drawing borders, or anything we want.
They were missing the latest strings and also still had strings that
had been deleted.
(Whoever commits the upcoming delivery should also sync that version)
This is so they will be updated when switching language with CTRL+F8.
Most of the editor notes are simple text that don't use any string
formatting. For the ones that aren't, some (saving and loading, changing
map size) reference variables that wouldn't change without initiating a
new note anyway. For the others, i.e. the ones that _do_ reference
variables that could easily be changed (tileset name, speed) by
switching the current room, we cache their values and use the cached
values when drawing the note. Unfortunately, this requires adding a
couple of ugly attributes to editorclass, but it'll be fine.
These are simple strings (no vformat), so we can just un-bake them to
make sure that cycling languages with one of them onscreen updates them
accordingly.
These weren't getting updated when cycling language with CTRL+F8. This
is because they would be already baked. Luckily, at least the bool
keeping track of whether or not to translate them in the first place
already exists, so we can just rely on that.
This makes it work pretty well. It basically just resets the state of
the limits check and starts from the first limit broken (if any), which
is behavior that makes sense to me.
Otherwise, without this, it seems to invalidate pointers and, on my
machine, start pulling strings from the language XML, which is
horrifying.
Not gonna lie, I am a bit disappointed at having to do this, because it
actually worked pretty well despite a few bugs depending on which
language you entered with. But that's only because I'm working with
the official translation files, which are in sync with each other.
With translation files that are completely arbitrary, it would be
apparent that switching languages during the cutscene test doesn't
really make sense. Like, at all. That's because the list of cutscenes is
populated entirely from language-specific XML and the cutscenes in them
are also from language-specific XML. So keeping the same position in the
menu doesn't really make sense, and keeping the same position in a
cutscene definitely doesn't make sense.
I saw that the only problem with cycling languages in a title screen
menu is that the menu options don't get updated. So I was like, we can
just recreate the menu, and then I was like "Sure, why not." So that's
what I did.
To accommodate the CTRL+F8 keybind in the language menu, it
automatically updates the menu option when you cycle it. This is because
otherwise using the keybind in the language menu wouldn't visibly update
the language, but it still actually does change your language, and that
can be seen by pressing Escape.
Also, the menucountdown needs to be preserved because otherwise
createmenu() resets it, even if it's the "same" menu (this behavior is
needed so that the menu that is shown during the countdown isn't added
as a stack frame which would make it a menu that could be returned to).
Originally, textcase was reset in scriptclass::translate_dialogue(),
which is called inside the `text` script command. However, this didn't
really work with the new on-the-fly text box translation system, and
that function is gone now, so I removed that and kind of forgot about
it.
Of course, this now causes a regression. Namely, that the text boxes
after the VVVVVV-Man sequence in the Secret Lab entrance cutscene are
not translated.
I can't reset the text case in `text`, as the scripts assume that they
can set the text case before `text`. So the next best thing is to reset
it in speak/speak_active.
This fixes a bug where some text boxes wouldn't update the displayed
button if the active input device changed from a keyboard to controller,
or vice versa. Namely, the "Press ACTION to continue" text boxes.
This removes Graphics::textboxwrap(), as it is now an unused function.
Additionally, this removes the return value of textboxclass::wrap(), as
it is also now unused.
This stores the original x-position and y-position of the text box, and
when a text box gets repositioned, it will use those unless a crewmate
position overrides it.
This is the original position of the text box, before centering or
crewmate position is considered.
This fixes a bug where a cutscene text box can be "shifted" from its
normal position via CTRL+F8 cycling if there is a translation that is
too long for the screen and thus gets pushed by adjust(). I tested this
with the text box in the Comms Relay cutscene that starts with "If YOU
can find a teleporter".
This is not applicable to function-based translations
(TEXTTRANSLATE_FUNCTION), because the responsibility of correctly
positioning the text box resides with the function.
This adds a debug keybind to cycle the current language forwards,
CTRL+F8. It also adds a debug keybind to cycle it backwards,
CTRL+SHIFT+F8.
This is only active if the translator menu is active (and so if the
regular F8 keybind is also active).
This is useful for quickly catching errors in translations and/or
inconsistencies between translations. In fact, I've already caught
several translation mistakes using this keybind which made me mildly
panic that I screwed something up in my own code, only to realize that
no, actually, it was the translation that was at fault.
For now, this is only meant to be used in-game, as text boxes get
retranslated instantly, whereas things like menu options don't. But menu
options will be retranslated on-the-fly in a later commit.
This fixes a problem where it would incorrectly format the text because
the width of the text box hadn't updated yet.
This fixes a bug where the jukebox informational terminal would
initially be created with too much padding in CJK languages, pushing the
text box offscreen, even though switching languages while the text box
is already open fixes it.
In order to be able to retranslate the game time text box in particular,
I had to create new variables to bake the saved time, since the existing
savetime variable is just an std::string. From there, the saved time can
be retranslated on-the-fly.
This adds an attribute to textboxclass to allow a text box to keep an
index that references another text box inside the graphics.textboxes
std::vector.
This is needed because the second text box of a "You have found a
shiny trinket!" or "You have found a lost crewmate!" pair of text boxes
explicitly relies on the height of the first text box. With this, I have
moved those text boxes over to the new text box translation system.
Since the update order now matters, I added a comment to
recomputetextboxes() that clarifies that the text boxes must be updated
in linear order, starting from 0.
This adds an assert to Graphics::textboxtranslate() to make sure that
callers don't accidentally provide a function when specifying a
translation type that isn't TEXTTRANSLATE_FUNCTION, because in that case
the function won't be used, and then it will make them scratch their
heads wondering why their function won't work.
And yes, I am stupid enough to blindly type TEXTTRANSLATE_CUTSCENE when
I meant to type TEXTTRANSLATE_FUNCTION. This assert has already caught
one of my mistakes. :)
These seemed annoying to do without copy-pasting, because I didn't want
to make a separate function for every single dialogue, and I didn't know
how to pass through the English text, until I realized that I can just
use the existing original.lines vector in the text box to store the
English text. After that, getting it translated on-the-fly isn't too
bad.
Just a small optimization.
For example, consider the calls in adjust(). After the first resize(),
the lines after only change the x-position and y-position of the text
box and depend on the x-position, y-position, width, and height.
However, resize() only changes the width and height if the contents of
the text box change, which after the first call, they don't. So remove
the second call to resize(), because it's completely unnecessary.
By similar reasoning, the second calls to resize() in centerx() and
centery() are unnecessary too.
This transfers the responsibility of the adjust() call to
applyposition().
This is because cutscene text boxes (TEXTTRANSLATE_CUTSCENE) will have
adjust() called, but all other text boxes won't. And I can't place the
adjust() call inside applyposition(), because adjust() also calls
applyposition(), and that leads to an infinite loop which leads to a
stack overflow, so I had to remove the applyposition() call from
adjust(), and replace the other existing call to
Graphics::textboxadjust() with Graphics::textboxapplyposition(), and
then remove Graphics::textboxadjust() function because it's no longer
used.
Several text boxes in the gamestate system are unused and are
untranslated. To prevent them from becoming empty when retranslating
text boxes, we need to save their original context by calling
graphics.textboxoriginalcontextauto() (which is just
graphics.textboxoriginalcontext() but automatically saving whatever is
already in the text box at the time).
With the new system of retranslating text boxes on-the-fly, this also
enables us to retranslate them whenever the player toggles Flip Mode.
This is relevant because the Intermission 1 instructional text boxes
refer to a floor when Flip Mode is off, but when it is on, it talks
about the ceiling.
This splits the text wrapping functionality of Graphics::textboxwrap to
a new function textboxclass::wrap, and with this new function, some more
text boxes can be moved to the new TEXTTRANSLATE_FUNCTION system.
Namely, Game Saved (specifically the game failing to save text box),
instructional text boxes in Space Station 1, and the Intermission 1
instructional text boxes.
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.
This is another piece of state that needs to be kept and re-played when
switching language, because a different language could change the
dimensions of the text box, which affects how it's centered.
Also, to make sure that crewmate positions override any text centering,
the scriptclass variables textx and texty should be reset in the
position and customposition commands.
Originally I did a straight deep copy of the original lines, but this
ignores the limit of either 12 or 26 lines in a text box. So we defer to
addline() which will enforce the limit accordingly, just like it would
do with the original text box.
This allows switching languages while a text box is on screen by saving
the necessary state for a text box to be retranslated when the language
is switched.
This saves the state of the position and direction of the crewmate that
the text box position is based off of (if applicable), and the text
case of the text box, the script name of the script, and the original
(English) lines of the text box. I did not explicitly label the original
lines as English lines except in a main game context, because
technically, custom levels could have original lines in a different
language.
Unfortunately, this doesn't work for every text box in the game.
Notably, the Level Complete, Game Complete, number of crewmates
remaining, trinket collection, Intermission 1 guides, etc. text boxes
are special and require further fixes, but that will be coming in later
commits.
This is for consistency with all other functions dealing with the latest
created text box. There are several cases in custom levels where these
functions can be called even though there are no text boxes on screen.
It doesn't make sense to change the alignment of all existing text boxes
when you're not otherwise able to mutate the text. Whereas the point of
the 'all' argument in setfont is to be able to animate text boxes using
fonts.
There used to be a problem with the setfont and setrtl script commands.
Namely, if you used them in between text boxes naïvely, without any
careful thought, then the fading out text box would suddenly gain the
font of the new one. A kludge solution to this was implemented by simply
blocking the script until the existing text box faded out before
switching the font or RTL, and shipped for 2.4.0.
However, a better solution is to simply bake the font flags in to the
text box, so that way, if the level font switches, then the text box
keeps its font.
This is only for custom levels, because in the main game, the font in a
text box needs to be able to change depending on language. But it seems
like custom level translations weren't much on the roadmap, and so even
the existing hack didn't support changing the font based on translation
(even though translation of custom level cutscenes is supported). So
baking the font flags into the text box here doesn't make things any
worse.
It also makes things better, arguably, by allowing multiple text boxes
to exist on screen at once with different fonts.
Maybe in the future we'll need a flag that specifies that the font
should change depending on language if a translation in said language
exists for the text box, or something like that.
For people that want to override the fonts of every existing text box on
screen, you can specify "all" as the second parameter of setfont or
setrtl to do so.
This fixes a regression where attempting to warp to ship with a trinket
text box open in glitchrunner 2.0, and then incrementing the gamestate
afterwards, would result in a softlock. This is a speedrunning strat
that speedrunners use.
The state lock wasn't ever intended to remove any strats or anything,
just fix warping to ship under normal circumstances. So it's okay to
re-enable interrupting the state by having glitchrunner enabled.
This bug was reported by mohoc in the VVVVVV Speedrunning Discord
server.
Just like I changed the number één to be capitalized as Eén instead
of Één (due to this being a special case), I forgot to do the same
for the onelifemode.
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.
Translators have been getting compensated for localization. Sometimes
they submit pull requests for their changes, but just because they do
doesn't mean that they won't get compensated.
The intent of this line was to make sure that any random contributor
wouldn't expect any compensation for their changes, so adding another
clause here still keeps that expectation while making it clear that it's
not like Terry _never_ compensates people. Even if, as a Swedish man
once sung, "Terry is a monster, I think we all know that".
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.
This adds the third_party/ directory to the list of paths that will
trigger a CI workflow when changed.
Not all updates to third-party dependencies will necessarily change
code, but we bump dependencies infrequently so there's not much of a
problem with triggering CI on every change to third_party/.
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).
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.
- Added a logo
- Cleaned up the description, answering the number one question I get asked about the source code
- Added link to the discord to direct potential contributors to
- Updated credits
- fixed some links in the credits (Magnus' site is currently down, but this is temporary)
This ensures that the game won't silently fail to start if it can't
initialize the filesystem. Instead, it will fail loudly by popping open
a message box (using SDL_ShowSimpleMessageBox).
The motivation for this comes from issue #1010 where this is likely to
occur if the user has Controlled Folder Access enabled on Windows, but I
didn't want to put in the work to specifically detect CFA (and not sure
if it's even possible if it turns out that the OS just gives a standard
"permission denied" in this case). At least any message box is better
than silently failing but printing to console when most users don't know
what a console is.
Fixes#1010.
This is the same as commit 70357a65bf
("Fix regression: Warp BG lerps in reverse direction"), but for the
tower background.
This bug is most visible when moving the camera in a tower using
invincibility, or holding down ACTION during the credits scroll.
This lets you hold down F to fast-forward the game if you have the level
debugger interface open (with Y) and the game isn't paused.
This is most useful for quickly skipping through cutscenes to test
something.
This code was introduced by Dav999 in
abf12632bb (PR #1077), but it contains a
memory error. I spotted this with Valgrind.
The problem comes from the fact that `max_codepoint` is calculated from
the width and height of the surface (which will have the same width and
height as the source `font.png` from the filesystem). Let's work through
an example using a typical 128 by 128 `font.png` and an 8 by 8 glyph.
`chars_per_line` is calculated by dividing the width of the image
(`temp_surface->w`, or 128) by `f->glyph_w` (8), yielding 16.
`max_codepoint` is calculated by first calculating the height of the
image divided by the height of the glyph - which here just happens to be
the same as `chars_per_line` (16) since we have a square `font.png` -
and then multiplying the result by `chars_per_line`. 16 times 16 is 256.
Now it is important to recognize here that this is the _amount_ of
glyphs in `font.png`. It is _not_ the last codepoint in the image. To
see why, consider the fact that codepoint 0 is contained in the image.
If we have codepoint 0, then we can't have codepoint 256, because that
would imply that we have 257 codepoints, but clearly, we don't. If we
try to read codepoint 256, then after working through the calculations
to read the glyphs, we would be trying to read from pixel columns 0
through 7 and pixel rows 128 through 135... in a 128 by 128 image...
which is clearly incorrect.
Therefore, it's incorrect to write the upper bound of the for-loop
iterating over every codepoint as `codepoint <= max_codepoint` instead
of `codepoint < max_codepoint`.
I was running the game through Valgrind and I noticed a memory error
where the game was attempting to read a pixel that was just outside the
image. Since this is an error that doesn't immediately result in a
segfault, I figured that it would be prudent to put in an assertion to
make it loud and clear that a memory error is, in fact, happening here.
Similarly, drawing to a pixel just outside the surface wouldn't result
in a crash, so I copy-pasted the check there too (with changes).
If you're in (5, 5) (1-indexed) and you resize the map to (4,5), the
editor stays in (5, 5). This has no real consequences, other than
possibly confusing the user, but it should probably be fixed anyway.
Turns out the string I fixed in the previous commit was also never
noticed in German. For that one, I simply used the wording that was
used in the old hardcoded-ACTION string (with my German knowledge,
I'm confident that's still correct).
I just discovered this: whereas 2.3 and older versions make gravity
and warp lines - when placed in the editor - stick out one tile
offscreen, the latest version stops the lines at the room border.
This change restores the old behavior, and it's a simple fix: the
refactored code was written to let tiles outside the room block
gravity/warp lines. Instead of all offscreen tiles blocking lines, now
there's a 1-tile padding around the room that will let them through.
The new localization-related credits are placed 5 characters from
the left border in the rolling credits (at x=40), which means the
limit was 35 8x8 characters. Which was broken by several languages.
So instead, move the string leftward a bit if it would run offscreen
otherwise.
If you go into the middle of the list of translators in the main menu
credits, then press Escape, and then go into the credits again, the
first page of the list may start at the wrong place, because while
game.translator_credits_pagenum was reset to 0,
game.current_credits_list_index wasn't. This is fixed now.
The header "Translators", as well as the language names, were using
PR_FONT_8X8, even though it was translatable text. This is now fixed.
(Also, the CJK spacing for the language names is now higher because
that looked nicer)
VVVVVV 2.2 only supported displaying characters 00-7F with its font
system. VVVVVV 2.3 added support for unicode, by supplying a font.txt
with all the characters that are in the font image. But 2.3 made
another change that I didn't immediately realize, even after reading
the code: if font.txt is not present, then the font is not assumed to
have _only_ 00-7F, but _all_ of unicode, as far as the image dimensions
allow.
However, an inconsistency I _did_ notice is how unknown characters
would be rendered in 2.3. If a font had a font.txt, then any unknown
characters would be shown as a '?'. If a font had no font.txt however,
then suddenly any unknown characters would just come out as a space.
I fixed this behavior with the new font system; but what was actually
happening for characters to come out blank is that characters up to
U+00FF, which _were_ technically in the font image but as fully
transparent, would be shown as they were in the image, and characters
beyond U+00FF wouldn't be shown since they were outside of the image.
I don't really want to show blank characters for any character between
80-FF if it is technically inside the image, because pretty much every
single ASCII-only font.png in existence (including the one in data.zip)
contains a blank lower half, just because the font in the game had
always had this specific resolution. (We didn't want to do things that
might crash the game because something was different from what it
expected...)
We have had some confusing occasions before with the old behavior where
the fonts weren't correctly packaged or something (like when the
Catalan translator was sent the first version of the translator pack,
or when people customize their fonts wrong) and special characters were
just blank spaces.
So, instead, for characters beyond 7F, I decided to consider them part
of the font, as long as they are not blank. That means, if a character
beyond the ASCII range has any (non-alpha-0) pixels, then it will be
added, otherwise it won't be. This is just to handle legacy fonts, and
the case where all fonts are missing and the one from data.zip is used;
new fonts should just use .fontmeta or .txt to define their characters.
The top of the programmers readme now says that you need the
translators readme to translate the game into a new language. Also,
since language file syncing now works to populate an empty language
folder, document that in the translators readme as well.
Two translators thus far have tried to populate initial language files
by creating a blank folder and then using the in-game sync option. For
example, see #1078.
That is not how the sync option was intended to be used, but it's
really close to getting everything, so I decided to just complete the
support by making sure numbers.xml is copied from English, and making
sure meta.xml is filled in with English text and not text from an
arbitrary language. Also, minor detail on plural form 1 being set to 1
by default if reset, so strings_plural.xml is fully consistent too.
Or well, lock yourself out if you don't have (easy) access to a
keyboard, like on Steam Deck.
In 2.3, this problem used to be much worse, since you could bind any
button to "menu" - which is actually also "return" in menus - and that
button could then no longer be bound to any other action, because
exiting the bindings menu had priority over assigning a different
binding. The result would be that people could have all their buttons
bound to "escape" with no way of undoing it or using their controllers
at all other than manually going into their config file to change it.
In 2.4, the most important bugs in the bindings menu are fixed, but
it's still possible to remove all your bindings from the "flip"
(confirm) action, meaning you can't navigate the menus anymore with a
controller to fix your bindings or even do anything.
There is one interesting part to all this: if an action has no buttons
bound to it at all when the game is started, then that action is
populated with the default button for that action. This is done for
each action separately, without accounting for the case where the
default button was already bound to another action which was not empty.
(This is something that the binding menu does try to prevent).
Therefore, having no buttons bound to "flip" while having A and B bound
to "menu", would result in A being bound to "flip" and A and B bound to
"menu".
That would still make you unable to enter the gamepad menu, since both
"confirm" and "return" are pressed in a row.
This commit fixes the specific situation where flip/confirm buttons are
also bound to menu/return, by removing all buttons that are in the flip
button list from the menu list. This means that, on Steam Deck, you can
still go to your bindings menu.
Seems like I made a mistake while originally writing the "make
autotiling base" code. This commit fixes the warp background turning
into solid tiles when you switch to a different tileset.
might consider adding an Español (latam) edit next year, but this is
enough for 2.4. We're using "Español (es)" instead of "Castellano"
because our translator prefers it
This commit adds translation credits to the game's end credits
screen. Note that this is not implemented into the menu credits
screen yet. The translator name list is subject to tweaks, and
additionally some localised strings ("Localisation Project Led by"
and "Pan-European Font Design by") run off the screen in some
languages (Catalan, Spanish, Irish, Italian, Dutch, European
Portuguese and Ukrainian) and will need to be addressed later.
I put a main focus on the first cutscenes in the game, changing the
first "Uh oh..." from something like "Oh dear..." to "Oh no..." to make
sure it always sounds right. (The real translation of "Uh oh" is "O-o",
but that seemed too easy to read wrong for the first line in the game
that I wanted to avoid it altogether.)
Textboxes created with graphics.createtextboxflipme() use PR_FONT_LEVEL
by default, but can be overridden with graphics.textboxprintflags() to,
for example, set PR_FONT_INTERFACE. This happens for the textboxes on
the Game Complete screen, which use interface text. The textboxes are
centered by setting the X position to -1 though, which means they're
solely centered based on the width of the first line, in the level
font (because the font hasn't been changed to the interface font yet).
Normally, this isn't a problem, because in the main game (where the
Game Complete screen usually appears), the level font is always equal
to the interface font. However, in custom levels you can still get it
(by calling gamestate 3500) and in that case some of the text may be
misaligned. This change fixes that by adding graphics.textboxcenterx()
to these textboxes.
As far as I can tell, these are the only textboxes that are centered
by just x=-1 despite changing the font afterwards.
If you had a pink space station background, and switched to a different
tileset, some solid tiles would be placed instead. This commit fixes
that by transforming the room into the basic autotiling tiles before
changing the tileset itself. The reason why I chose this solution is
because it will help with a future change, being unhardcoding warp zone
backgrounds (which'll help with custom autotiling, if that becomes a
thing.)
Now that the language files are fairly stable, we should be able to do
this without any accidental reverts taking place (if any do happen, it
should be easy to see and prevent)
With the recent change to drawing overlays (images and sprites) from
PR #1058, it's starting to get a bit hairy. This names the conditionals
responsible for determining if the text box is transparent (checking
that all of its RGB is 0) and if overlays should be drawn or not (which
is now either when it's opaque or transparent).
Textsprites and textimages no longer wait for the opacity
value in order to display within transparent textboxes.
Text sprites in normal opaque textboxes are not affected
by this change.
Fixes#1057.
Based on Ethan's hunch, I simply removed the format comparison that
decides whether to halt and restart, or reuse the voice. Voices are
now always restarted when playing a new track.
This also simplifies the code somewhat: `MusicTrack::musicVoiceFormat`
was now no longer used, and an `if (!IsHalted())` was no longer
necessary because `Halt()` already does that. So those are now removed
as well.
This string is used both in time trials (alongside "MORTS :" and
"BLINGS :") as well as outside time trials if you enable the in-game
timer. In English, this looks like "TIME:1:23.45". Since French adds
a space before the colon, it will look like "TEMPS :1:23.45" instead.
Therefore, I've added a space after the colon as well.
At first my CJK changes also misaligned this sprite, and my solution
that time was to position the textbox higher depending on the height
of the textbox, so it would be centered around the crewmate sprite
(which stayed at a hardcoded place onscreen). Recently, #987 changed
these sprites to be relative to the position of the textbox instead of
relative to the screen, which is much more logical, but it stopped
centering these sprites again. But it's an easy fix: simply account for
the extra-added height when adding the sprite in.
This hasn't been relevant for years now. Even in 2.3, this wasn't
relevant, but we added a disclaimer saying that it only applies to 2.2.
But now issue #1052 has been opened specifically pointing to this
section as something that should be removed. Therefore, I'm removing it.
This fixes a regression caused by PR #923 (the PR that moved rendering
to be GPU-based) where the interpolation of the horizontal and vertical
warp backgrounds (in over-30-FPS mode) was in the wrong direction, which
makes them look blurry.
This happens because the arguments to the `lerp` function were in the
wrong, reverse order.
On the VVVVVV Discord server, Ally raised the argument that they were in
the same order before she made the changes; therefore the previous code
was also incorrect and it wasn't her fault. However, this argument is
incorrect, because in that case, the reverse order _is_ the correct
order.
The reason that it's now the wrong order is because the output of `lerp`
is now being used as the argument to a source rectangle. Previously, the
output of `lerp` was being used as the offset argument to
`ScrollSurface`, which is analogous to being a destination rectangle.
Fixes#1038.
The main issue was mostly that we have to build C files as C++ in some
cases, and extern "C" wasn't being used everywhere, so linker errors
popped up. The rest is the usual tedious VS2010 stuff like casting void*
to other stuff, so this commit as a whole is pretty boring!
*subject to changes
Also, Traditional Chinese is current using the Simplified Chinese graphics, which is acceptable but not ideal:
Obey -> 服從 (ok to use simplified 服从)
Lies -> 謊言 (ok to use simplified 谎言)
The other words are the same for Simplified Chinese and Traditional Chinese.
commit 3d6802add8
Author: Dav999 <dav999.tolp@gmail.com>
Date: Thu Oct 19 17:16:01 2023 +0200
Change AVOID to FAINIC in Irish
commit 21fd84f479
Author: Dav999 <dav999.tolp@gmail.com>
Date: Thu Oct 19 17:04:27 2023 +0200
Partial final strings for Esperanto
This does not yet include the new localization credits, but I already
had all the other strings.
commit 45382a358c
Author: Dav999 <dav999.tolp@gmail.com>
Date: Thu Oct 19 17:01:30 2023 +0200
Final strings for Dutch
I also decided to change AVOID from ONTWIJKEN to ONTWIJK, to make it
a bit more fitting as if it's an actual word enemy with length
restrictions, heh. (Not that it's an abbreviation - it's just an
imperative instead of an infinitive. And those terms I had to look up)
This commit adds new debug lines while you're NOT hovering over an
entity or a block. Additionally, coordinates are now displayed smaller,
to not take up as much vertical space.
The level debugger is toggleable in playtesting mode by pressing Y.
You can toggle whether or not the game is paused inside of the debugger
by pressing TAB. The debugger screen allows you to see entity and block
properties, and allows you to move them around.
The hardest room used to be stored as a room name in whatever language
it was in when you last died enough times to break the record (before
localization, that was always English). Even after localization became
a thing we could get away with this since we only had a single font,
but now we might have actual question marks appearing when the new font
doesn't support characters from the old language.
Therefore, this commit adds more info about the hardest room to save
files - everything that is needed to know in order to do the
translation at display time. These are hardestroom_x and hardestroom_y
for the room coordinates, as well as hardestroom_specialname to mark
special names, in addition to changing the stored room name back to
English. I've also added hardestroom_finalstretch in case we later
decide to drop the English name as a key and rely on just the
coordinates (even though I think that change itself would be more
complicated than any simplification it would accomplish, and I don't
think it's necessary, but better to have it if we do need it later)
As described in #1016, there used to be a bug that inflated
levelstats.vvv in 2.3, which was fixed in 2.4, but there was no way
for inflated files to get smaller yet.
This commit changes the storage of levelstats from a std::vector of
structs to a std::map, so that uniqueness is guaranteed and thus the
stats can be optimized automatically. And it also simplifies *and*
optimizes the code that handles the levelstats - no more big loops that
iterated over every element to find the matching level.
(Farewell to the "life optimisation and all that" comment, too)
I tested this with both my own levelstats.vvv, as well as some inflated
ones (including Balneor's 93 MB one) and saw this code correctly reduce
the filesize and speed up the levels list.
Fixes#1016.
The declarations of `std::vector<std::string> customlevelnames` and
`std::vector<int> customlevelscores` are made quite early in the
function, commented with "Old system", but the place where the old
system is processed is after a big chunk of code that processes the new
system (and indeed never uses these vectors). So for readability,
they're now closer to where they're used.
`levelcomplete` and `gamecomplete` were hardcoded using textbox colors
which were offset by 1. This PR fixes that, no longer requiring
slightly-off colors, and instead adding a new property to textboxes
which tell the game to display either level complete or game complete.
This commit adds a system for displaying sprites in textboxes, meant to
replace the hardcoded system in the main game. This does not support
levelcomplete.png and gamecomplete.png yet, which will most likely just
be special cases.
This ensures loading a 2.4 save in the English-only 2.3 or earlier
doesn't result in missing characters because a translated area name
appears in the save file. We are not reading from <summary> anymore
in 2.4.
The way this is done is by not translating the area names inside
mapclass::currentarea(), but at the callsites other than the one which
saves the <summary>.
For both `tele` and `quick`, I removed these attributes of class Game:
- std::string *_gametime
- int *_trinkets
- std::string *_currentarea
- bool *_crewstats[numcrew]
All this info can now be gotten from members of Game::last_telesave and
Game::last_telesave. I've also cleaned up the continue menu to not have
all the display code appear twice (once for telesave and once for
quicksave).
RIP "Error! Error!" though lol
This is what got saved to the area part of the <summary> tags, and it
was specifically set upon pressing ACTION to save in the map menu.
Which meant tsave.vvv may not get an accurate area name (notably
"nowhere" if you hadn't quicksaved before in that session) even though
it's not displayed anywhere so it didn't really matter. But this
variable can be removed - there's only one place where <summary> is
written for both quicksaves and telesaves, so that now gets the area
at saving time.
Fun fact: custom level quicksaves also have a <summary> tag, and it's
even less functional than the one in tsave.vvv, because it stores
whatever main-game area name applies to your current coordinates.
So I simply filled in the level's name instead (just like what the
actual save box says).
Game::telesummary and Game::quicksummary stored the summary string for
the save files - which is the <summary> tag that says something like
"Space Station, 10:30:59". The game only ever displays the quicksave
variant of these two, for "Last Save:" on the map menu's SAVE tab.
So the telesave has a <summary> too, but it's never displayed anywhere.
(In fact, the area is often set to "nowhere"...)
However, the summary strings have another function: detect that both
the telesave and quicksave exist. If a summary string for a save is
empty, then that save is considered not to exist.
I'm refactoring the summary string system, by making the new variables
Game::last_telesave and Game::last_quicksave of type struct
Game::Summary. This struct should have all data necessary to display
the summary string at runtime, and thus translate it at runtime (so
we don't store a summary in a certain language and then display it in
the wrong font later - the summary can always be in the current
language). It also has an `exists` member, to replace the need to
check for empty strings.
The <summary> tag is now completely unused, but is still written to
for older versions of the game to read.
(This commit does not add the new string to the language files, since
Terry now added it separately in his own branch)
It used to take a single int: the area number returned by
mapclass::area(roomx, roomy). All uses of currentarea() were called
with an extra area() call as its argument. Additionally, there's a
good reason why currentarea() should have the room coordinates: in one
of the cases that it's called, there's a special case for the ship's
coordinates. This results in the SAVE screen in the map menu being able
to show "The Ship", while the continue screen shows "Dimension VVVVVV"
instead. Therefore, why not put that exception inside currentarea()
instead, and remove a few callsite map.area() wrappers by making
currentarea() take the room x and y coordinates?
Since #1047 was merged, we now make the user build the SDL prefab
themselves (as SDL does not publish Maven packages yet). Here are some
instructions for doing that.
Whenever I'd compile on Windows, I'd see the literal text "%cs" in the
main menu instead of the commit date. I never thought much of it (at
least it runs, and the date only shows up in development builds). Now
that I've also seen a screenshot from Terry with it, I decided to look
into it further. Looks like it's a format string that our gits on
Windows aren't recognizing for whatever reason - probably because
they're too old. I have git version 2.23.0.windows.1, and checking its
help page for `git log`, under PRETTY FORMATS, %cs is missing as an
option, while some other options are still there. So the option was
probably added sometime between that version and 2.34.1, which is the
one I have on Linux, where %cs does work.
Luckily, %cd with --date=short seems equivalent, and better supported,
so we can just use that instead.
Recent versions of CMake emit the following:
CMake Deprecation Warning at CMakeLists.txt:4 (cmake_minimum_required):
Compatibility with CMake < 3.5 will be removed from a future version of
CMake.
Update the VERSION argument <min> value or use a ...<max> suffix to tell
CMake that the project does not need compatibility with older versions.
Reading the documentation further, adding a max refers to the max
version compatibility of CMake _policies_. Adding a max of 3.5 makes the
warning go away, so it seems that the warning is more about policies
than anything else.
This will still work on 2.8.12 as the extra dots will be seen as a
version component separator, ignoring the max version.
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.
It'll start working in the next commit... See the description there.
(This commit does not add the new strings to the language files, since
Terry now added them separately in his own branch)
I recently changed my GitHub username from Dav999-v to Daaaav, and
today I moved the Ved repo from GitGud.io to GitHub. This commit
updates the references to both my username and the Ved repository.
The intention of the recent refactor was to make it so that the
temporary surfaces would be allocated only once, when the mode is
enabled, and be freed upon exit.
To do this, Graphics.cpp owns the pointers, and passes them to
ApplyFilter to modify. Except ApplyFilter doesn't actually modify the
pointers, because it's only a single pointer, not a pointer-to-pointer.
So every frame of rendering it would actually be creating a new surface
and leaking memory.
To fix this, they need to be pointer-to-pointer variables that get
modified.
I also added error logs in case the surface creation failed.
Right now, Windows assumes all our console output is code page ????.
That means our UTF-8 output appears mangled. (I ran into this while
testing IME text input by outputting strings to the console)
For a moment I was scared we'd need to do UTF-16 conversions and call
Windows-specific print functions like WriteConsoleW() in our vlog
functions, but fortunately SetConsoleOutputCP(CP_UTF8) works just fine.
Since VVVVVV 2.3, time trial best times are stored not just with the
number of seconds, but also the number of frames. However, there was
no room to display it with the old design of the time trials screen.
Now there is, so it can easily be displayed now with a small change!
Unset frames are stored as -1, which fits perfectly into the frames
argument of help.format_time(), because in that case the amount of
centiseconds is not shown.
It should be noted that opening VVVVVV 2.2 will instantly wipe your
frames records, as described by #1030. But many people will likely
never open 2.2 anymore.
Some people might be confused by the reference to M&P in the
instructions referring to downloading data.zip (#1026).
data.zip is the same between M&P and full versions of the game and is
orthogonal to which version of the game is built. Building M&P just
requires uncommenting `#define MAKEANDPLAY` in `MakeAndPlay.h`.
So clarify that you can grab data.zip from your existing copy of the
game, or from the Make and Play _page_ (not necessarily the Make and
Play edition of the game), and add instructions for building the M&P
version.
Closes#1027.
This serves as a file to help others in building the C++ Android version
for themselves.
These instructions are what I figured out to get it to work for me, and
should be kept up to date.
This is somewhat convenient for development, as it means that Android
Studio will only do a native build for the architecture of the device
being used for testing.
This is ignored for AABs, so it won't affect release builds (at least
for Google Play).
I took a very critical look at all the menus, to make sure they're all
clear and easy to read. I mainly simplified some explanations and
solved some small issues.
Well, 100% up-to-date with current upstream at least, there's some more
strings to be added soon, including "{area}, {time}" from #1018, a new
menu option related to translatable graphics files, and definitely some
credits stuff.
These were causing false alarms in translations for one reason or
another (either to force translations to not wordwrap for style
reasons, or to stay on the safe side if an adjacent string was also
long), so they can be raised now.
In 8484b36198, I fixed the title screen
showing up if you go to the language screen from in-game, while not
having any language files. There was also one other possible way to get
this to happen that I missed though: if you do have language files, and
you have not set your language yet, and you start a playtest via the
command line (e.g. by using Ved), and you then change the language
from the in-game options. That is now fixed.
I really thought I was going to need to block changing the language
in-game altogether, but activity zone prompts are now fixed and the
only obvious problem I can think of right now is having a dialogue
open, so I just disable the language option if a textbox is displayed.
(like how the map menu only has the save option if a script is running)
The translations for the prompts used to be looked up at creation time
(when the room is loaded or the activity zone is otherwise spawned),
which meant they would persist through changing the language while
in-game. This would look especially weird when the languages you switch
between use different fonts, as the prompt would show up in the old
language in the new language's font.
This problem is now fixed by letting the activity zone block keep
around the English prompt instead of the translated prompt, and letting
the prompt be translated at display time. This fixes a big part of the
reason I was going to disable changing the language while in-game; I
might only need to do it while textboxes are active now! :)
If someone makes a build of the game without copying the correct
folders, their version will have no translations, and display some text
wrong (like credits or button glyphs, or any custom levels that rely on
characters in the fonts being there). So I added a message in the
bottom left corner of the title screen to warn for that.
If you've never set a language before (<lang_set> is not 1), then the
language screen will show up before the title screen. Selecting the
language will then make the title screen show up.
If no language files are present, the old logic for handling this was
to simply show the language screen at startup anyway, and let it
display the error message that language files are missing, as a warning
that the game is not packaged correctly. However, this logic has two
flaws:
- If the user has ever had language files and set a language before
(in a VVVVVV on that computer), the warning element is gone because
the language screen is not shown in that case - the game is simply in
English
- If the user has never set a language before, and then goes to the
language screen later via the menu, they will be sent to the title
screen, even if they were in-game. The main menu will also be broken.
The new way is to not show the language screen at startup if language
files are missing, and to change the logic so that you will only be
sent to the title screen if you actually haven't seen the title screen
yet.
I will also add a proper warning that fonts or language files are
missing by adding a message in the bottom left corner (in place of the
MMMMMM installed message).
strings.xml in Polish didn't yet contain the Steam Deck strings, and a
few limits changes.
Also, "Font: " was translated as "Czcionka:", missing the space. This
is fixed now.
We had two separate cases for translators for this string (a
"TO UNLOCK:" one and a secret lab trophy one) but I forgot to use
the latter in the code, so both places in the game were using the
former. This is now fixed.
And another new language!
This uses the same font as Simplified Chinese. As such, I changed the
displayed name of the font (in the level editor) from 简体中文 to 中文.
Another new language! And this is a very interesting one, since it's
based on Nicalis' translation for 3DS and Switch (with their go-ahead).
Which means I had to convert between two completely different
language file formats, which was some work, but it's totally worth it!
Naturally, there are a lot of missing strings, so a translator will
still need to fill in all the blanks (and maintain the translation for
new strings of course)
The next commit will be the initial commit adding the Japanese
translation, so here's the font!
Specifically, it's k8x12L (2021-05-05), and it can be downloaded (in
non-VVVVVV format) from https://littlelimit.net/k8x12.htm
New language! This uses the 10x10 font added in the previous commit.
This has some minor changes from the delivered version:
- Synced language files, and thus added max_local attributes
- Removed leftover Catalan strings in strings_plural (and reduced to
just one form)
- Aligned terminal_finallevel
The Korean localization has been delivered (next commit), so this
commit adds the font for it! The chosen font is Galmuri 9 (specifically
GalmuriMono9, version v2.38.7). It's licensed OFL, and since I had to
convert it to VVVVVV's bespoke font format and shift characters around,
I think we are now bundling a Modified Version of the font, and it has
to use the same license. Including it as font_ko_license.txt and
clearly indicating that the copyright came from the Original Version
should be more than enough.
This version is a bit more polished than the placeholder one posted on
Discord, namely (non-CJK) characters were shifted to fit into their
10x10 bounds as much as possible, and notably the , and . characters
were shifted 2 pixels to the right.
This adds the following new strings from #993:
- The level editor is not currently supported on Steam Deck, as it
requires a keyboard and mouse to use.
- The level editor is not currently supported on this device, as it
requires a keyboard and mouse to use.
Unfortunately this means most languages won't be quite 100% anymore
for a bit, and updates come in which don't have this string yet.
But at least we can track it really well. In the next couple of
commits, when a language is updated with all new strings except for
these, I'll call them 99.9% instead of 100% (I did not get an actual
percentage).
Originally, we were using just the hint, but this didn't work well for
toggling VSync (see #831). Then I added SDL_RenderSetVSync to SDL, and
used that instead for toggling, but we were still setting the hint on
game startup.
Now, to keep things consistent, and just to make sure we don't get any
surprising behavior should things change in the future, this makes it so
game startup uses SDL_RenderSetVSync too.
This fixes#1013 by axing the use of SDL_HINT_RENDER_SCALE_QUALITY and
instead using SDL_SetTextureScaleMode.
The hint is unwieldy to use, and since #923, has resulted in a
regression where starting the game in filtered mode then switching to
nearest results in scaled textures still being filtered.
The proper solution is to use SDL_SetTextureScaleMode on the two
textures that are drawn to the final screen: gameTexture and
tempShakeTexture.
This commit fixes an obscure bug with `destroy(moving)` and
`destroy(disappear)` where, when looping through entities, the code
doesn't actually check what the entity is before trying to destroy the
block underneath it.
To fix this, we just put the block-destroying code *inside* of the
check, instead of being outside of it.
I also fixed the code style because it was horrible.
Closes#925.
My fix here is to delay the font change until all fading-out textboxes
have disappeared. See it as adding a sort of `untilbars` or `untilfade`
for text box fadeout, into setfont.
This doesn't prevent every possible way to change the font of an
existing textbox, but you would need to use internal scripting to still
do it (and basically be doing it on purpose) - the problem in
simplified scripting when you simply do textbox-setfont-textbox is
gone.
Showing the option on the "play a level" option feels to me as though
inexperienced players would think they're not supposed to open the
player levels, because the message says editor levels are unsupported,
right? But the message is only referring to the level editor, so in my
opinion, it's clearer to only show it there.
This commit removes the `NO_EDITOR` and `NO_CUSTOM_LEVELS` defines,
which cleans up the code a lot, and they weren't really needed anyways.
This commit also disables the editor on the Steam Deck, and adds a
program argument to re-enable the editor, `-enable-editor`.
For some reason, the credits button was always specifically removed from
M&P builds. After some discussion with Terry Cavanagh on the VVVVVV
Discord server, we agreed that there was no reason this should be
removed. So, it's getting put back in.
This puts the code to fix mouse coordinates in stretch mode directly
inside KeyPoll::Poll, preventing the need for any other instances of
mouse coordinate usage to copy-paste code.
If this text on the time trial results screen would overlap with the
time value, all rank labels will be displayed on the header line
instead ("TIME TAKEN:" etc). This works because the overlap with the
time most likely only happens with CJK fonts (where the time will be
very wide because of the font size) while strings like "TIME TAKEN"
take up very little space due to only needing 4 characters or so for
the same information.
- ERROR/WARNING screen title was overlapping with message
- Crewmate screen names and rescued statuses were overlapping with each
other
- Textboxes on Level Complete screen were overlapping with each other
and the crewmate was not vertically centered in the box
- Some strings were running into each other in flip mode, instead of
being moved out of each other (PR_CJK_HIGH and PR_CJK_LOW worked the
wrong way around because of FLIP macros being applied to Y coords)
- In-game esc menu was "bouncy" with selected menu options because of a
hardcoded 16 pixel offset
- Bindings in the gamepad menu were overlapping with each other
- Some Super Gravitron "Best Time" labels and values were a little too
close
This will be needed for the Simplified Chinese translation, of which
the first version has just been delivered!
This is the first language with a font bigger than 8x8 (this is 12x12),
so it might be a little rough in some places. Most of the game is
already prepared for it, though!
The only changes I made from the previous version (which was uploaded
on Discord a few times and also sent to the translator) was the …
character - it's often used twice in a row, and it was a little uneven
(looking like - - - - - - instead of - - - - - -); and the
semicolon, which was missing some pixels.
Two changes:
- The labels on the Game Complete! screen for number of trinkets/deaths
/time etc have been moved two pixels to the right, and had their
limits increased by 1 character
- The inaccuate limit for "quit to main menu" has been increased
These are errors and issues that have been reported, but are fixable by
us without needing to involve the translator in the fix, without too
much risk of accidentally breaking grammar rules.
The full list of fixes:
- Fixed some menus going offscreen by removing unneeded words (I'm
fairly confident after some cross-referencing and research) in
Portuguese BR, Portuguese PT, Spanish, and French
- Set 0 to singular in numbers.xml in Portuguese BR and French
- Removed the ** from dimensional stability generator to make it not go
offscreen, and aligned the text better, in Portuguese BR and Spanish
- Fixed some small casing and spacing errors in Portuguese BR, French
and Welsh. Think of misplaced spaces or sentences starting with a
lowercase letter
- Fixed a limit break in Spanish (the menu button already drops the
word "de" in "retraso de la entrada", so the title can too, right?)
- Corrected some typos in French and German (je continuer->je continue,
9: Finde->9: Feinde and NENÜ->MENU)
- Fixed spacing and alignment in teletype terminals in Welsh (like the
dimensional stability generator)
923efe54d6 added a note to the PR
template, but the best place is probably in the translators readme.
Hopefully this will make sure we no longer get people eager to start
a translation because nobody else had started one yet, and then have to
be disappointed with a reply from us saying we can't accept voluntary
translation contributions.
Another new language! It's based off of the old translator pack so
it'll need to be updated later. This commit also includes some fixes
to the originally submitted version, mainly:
- "2.0" and ".99" were broken by excel(?), now fixed
- Removed traces from extraneous rows and columns
- Small human errors
This includes the update received 2023-06-09.
For now, Terry has decided that translations can't be done by just
anyone. This note here should discourage at least some people from
making pull requests to add their own translations.
If you provided any one of -playx, -playy, -playrx, -playry, -playgc, or
-playmusic in command-line arguments for command-line playtesting, then
the game would always try to play music, even if you passed a negative
-playmusic. This wouldn't do anything in that case, unless you had
MMMMMM installed, in which case it would play MMMMMM track 15
(Predestined Fate Final Level) due to the legacy wraparound bug.
To fix this, only play music if the track provided is greater than -1.
Additionally, to prevent it from playing Path Complete by default if you
specify any of the other save position arguments but n ot -playmusic,
it's now initialized to -1 instead of 0.
Turns out `graphics.drawrect` exists. Well, not anymore!
This was another function from before the renderer rewrite which tried
to draw a rectangle by using four filled rectangles. We can draw
outline rectangles properly now, so let's make sure everywhere does it!
As #974 states, the lab background only ever uses the first generated
value from `backboxint`, so let's change it to a normal variable
instead of an array. Also, stars don't need their width/height set to
2 constantly... they never change in the first place, Terry!
Some code still used rectangles to draw things like lines and pixels,
so this commit adds more draw functions to support drawing lines and
pixels without directly using the renderer.
Aside from making generated minimaps draw points instead of 1x1
rectangles, this commit also batches the point drawing for an
additional speed increase.
This fixes a bug where if a track was resumed, pausing it by unfocusing
the window (if enabled, of course) would not resume it after refocusing
the window.
This happens because resuming the music doesn't change currentsong back
from -1, and the window refocusing code checks that currentsong isn't -1
before resuming music.
haltedsong is only used when resuming music. It is set back to -1 when
resuming music or when playing a new track.
Autotiling was a mess of functions and if chains and switch statements.
This commit makes autotiling better by assigning each direction to one
bit in a byte, giving each different combination its own value. This
value is then fed into a lookup table to give fine control on which
tiles get placed where.
The lab tileset can now use the single tiles which were before unused
in the autotiler, and the warp zone's background tool now places the
fill used in the main game.
- Some levels used chars < 0x20 as non-collapsible spaces, those would
now show up as [?]
- I recently found out how making characters < 0x20 6 pixels wide
doesn't work if they're missing from the font altogether
Therefore, the font now starts at 0x00 again instead of 0x20, like it
used to.
Arguably it was an advantage that the game would look extremely messed
up if you made a mistake with the fonts. In particular, a common
mistake could be to copy the new Unicode font.png, but forget to copy
the corresponding font.txt. However, 2.3 didn't come with the unicode
font, 2.4 will, so it'll be a lot less common for people to need to
manually copy the font. And if they do, it's probably for their own
level, and they have something in mind for the font, and if it doesn't
work they'll know fast enough when whatever they're planning doesn't
work (and it would only affect their own level's text, not any menus).
New language! It's still based off the old translator pack so the
newest strings haven't been translated yet, but the language files are
synced and we still need to get these updated in most other languages
either way.
This does include the update delivered on 2023-05-24.
A little while ago the term "AGOKLAVO" (action-key) was chosen to
replace the older "AGBUTONO" (action-button). However, in a recent
language update, I mistakenly used the older term in a new string.
This has been fixed.
Also note that it's written in the accusative case for this string
(with an "N" suffixed) since it is always used as the object of the
sentence where it appears ("Premu AGOKLAVON..." = "Press ACTION...").
This fixes a regression where main game teleporter icons (which would be
target icons if flag 12 was on) would be rendered on the minimap after
loading from a custom quicksave.
This is because this was always enabled when loading from a custom
quicksave, but the game didn't start rendering them until PR #898, which
de-duplicated the minimap rendering code.
The best fix here is to just not enable the teleporters when loading
from custom quicksaves.
This adds an anonymous enum for time trial indexes (e.g. the bestrank,
bestlives, etc. arrays and timetriallevel), and replaces all integer
literals with them.
Just like the unlock arrays, these are indexes to an array in XML save
files, so the numbers matter, and therefore should not use strict
typechecking.
This adds an anonymous enum for the unlock and unlocknotify arrays and
unlocknum function, and replaces all integer literals with them.
This is not named and thus cannot be used for strict typechecking
because these are actually indexes into an array in XML save files, so
the numbers themselves matter a lot.
This replaces the swngame int variable with a named enum and enforces
strict typechecking on it.
Strict typechecking is okay here as the swngame variable is not part of
the API surface of the game in any way and is completely internal.
And just to make things clear, I've added a SWN_NONE enum to use for
initialization, because previously it was being initialized to 0, even
though 0 was the Gravitron.
The clock on the Game Saved quicksave screen has always been upside-down
in Flip Mode. And technically, the trinket was too, but this was
unnoticeable because the default trinket sprite is symmetrical.
To fix this, draw flipsprites.png if these sprites are being drawn in
Flip Mode instead of sprites.png.
There's not any ill effects of it being initialized to 0 that I am aware
of (because in most cases, it either gets overwritten anyways or there
isn't a track playing in the first place), but it shouldn't be 0,
because that's Path Complete, so fixing this just in case.
This adds an anonymous enum for sound effects and replaces all calls to
music.playef that use integer literals.
This is not a named enum (that can be used for strict typechecking)
because sound effect IDs are essentially part of the API of the game -
many custom levels use these numbers. This is just to make the source
code more readable without needing a comment to denote what number is
what sound.
This adds an anonymous enum for music tracks and replaces all calls to
music.play and music.niceplay that use integer literals. Additionally,
this is also done for integer literals for cl.levmusic (except 0) and
music.currentsong where appropriate, but _not_ the music areamap because
that would not make it look very aesthetically pleasing in the code.
This is not a named enum (that can be used for strict typechecking)
because music track IDs are essentially part of the API of the game -
almost every custom level uses these numbers. This is just to make the
source code more readable without needing a comment to denote what
number is what track.
This adds a "- Press {button} to skip -" prompt to both the credits and
ending picture sequences.
It was always possible to skip them by pressing Enter, but not many
people knew this. In fact, even I didn't know this until I saw Elomavi
do it a year or so ago. So it's not really intuitive that this is
possible.
The prompt only shows up if you've completed the game before, and
disappears after two seconds similar to the "[Press {button} to return
to editor]" text.
Unfortunately, given how the game works, game completion is detected
based on if you have unlocked Flip Mode or not. At this point, the
unlock for the game being completed (unlock 5) will already be set to
true no matter what during the Plenary fanfare, but the Flip Mode unlock
(unlock 18) won't be until the player hits "play" on the main menu. As a
special case, the prompt will always show up in M&P (because Flip Mode
is always unlocked in M&P).
This prevents deleting telesaves and quicksaves in special modes and
custom levels.
Otherwise, rolling credits in a custom level would still delete the main
game quicksave.
If you had the map button held before the game transitioned to the
credits and ending picture sequences, then you wouldn't be able to skip
them, because those gamemodes don't have logic to detect when you've
released the map button.
To fix this, just implement the map button release logic.
We do need a better input system soon...
This command was changed from setactivityposition(x,y) to
setactivityposition(y), but there's a small problem here:
```diff
else if (words[0] == "setactivityposition")
{
- obj.customactivitypositionx = ss_toi(words[1]);
obj.customactivitypositiony = ss_toi(words[2]);
}
```
This meant that the function still took two arguments, the first of
which was unused and the second of which was the Y position of the
activity zone. This is now fixed.
This fixes a long-standing bug where it's possible to play music during
the Game Over screen in No Death Mode. All you have to do is die while
music is fading out from one area to the next.
The easiest way to do this is in the entrance to Space Station 2, since
there's a music change to Passion for Exploring in Outer Hull (you will
need to go into the zone far enough to activate Pushing Onwards first),
which also contains spikes to die on.
Basically, it's a simple oversight because the nicefade system relies on
music fading out to start playing the next track, but in this case, No
Death Mode fades the music out without accounting for that. It's best to
just disable nicefade entirely when dying in No Death Mode.
Thanks to KSS for reporting this bug.
This calls Game::savestatsandsettings() after unlocking Master of the
Universe (the achievement for completing No Death Mode), instead of
before.
This is not a big deal since Game::savestatsandsettings() is called
anyway whenever the game is gracefully closed since 2.3, but it helps to
make sure people won't lose their achievement data.
2.3 already made it so that if you ran the `rollcredits` command during
in-editor playtesting, you wouldn't be returned to the title screen
while losing unsaved level changes. But there are plenty of other ways
to go back to the title screen from in-editor playtesting too. Namely,
gamestate 1015 (the gamestate after completing a level) and 82 (time
trial complete).
So just add the appropriate checks to those gamestates, and add a
catch-all check in Game::quittomenu(). Additionally,
Game::updatecustomlevelstats() should not update custom level stats
during in-editor playtesting (otherwise it would still happen even if
the game didn't bring you back to the title screen).
Editor notes will also be shown if the game prevents you from going to
the title screen.
Also, just to make things clear, I also added a level note for when the
level is completed during in-editor playtesting. This is just to make it
clear in cases where it might not be obvious that the game returned you
to the editor for this reason. E.g. you have a terminal that calls
gamestate(1013) in a level with 0 custom crewmates, but when you
activate it, it looks like the terminal didn't work for some reason and
just brought you back to the editor. But that's just only because you
literally just completed the level.
This fixes a bug where the wrong music can play on the title screen, as
reported by AllyTally on Discord.
The bug can be triggered by triggering a room transition right as
game.quittomenu() is called (which is easiest to achieve by placing the
player on an oscillating/"out of bounds" room border in a custom level
so they go back and forth between two rooms every frame, and triggering
gamestate 1013, which starts a fadeout to menu if all custom crewmates
are rescued).
When this happens, game.quittomenu() calls script.hardreset(), but the
rest of the frame still executes, even though we set game.gamestate to
TITLEMODE too (because game.quittomenu() was called by
game.updatestate() which was called by gamelogic(), and game.gamestate
is only checked at the start of the frame). This ends up triggering a
room transition, and since map.custommode is guaranteed to now be off
(because of script.hardreset()), the main game music area code kicks in,
and plays something that isn't Presenting VVVVVV.
The bug here is that we're resetting too early when we still have the
rest of an in-game frame to execute. So, instead, we should only reset
at the end of the frame, and this can be achieved with a defer callback.
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).
game.quittomenu() correctly resets state, as it's the function that's
always used when quitting to menu. This fixes a bug where if a level
with assets failed to load, it wouldn't unload the assets.
This exports the previously-internal setLevelDirError function in
FileSystemUtils and uses it for if a level is not found or there was a
parsing error. Previously, if a level failed to load in these ways, it
would take you to the error screen with no error, while printing it to
the console. But this makes it more user-friendly.
As a bonus, the text is localizable, just like the existing usage of
FILESYSTEM_setLevelDirError for if a path couldn't be mounted.
After the scriptclass::startgamemode refactor, a lot of common code is
still being executed even if the level loading failed. This sets the
game-gamestate to TITLEMODE in gotoerrorloadinglevel(), and also returns
early just in case.
Fixes#975.
This is quite a simple bug: If you edit a script, then close it, you
will no longer be able to use the mute buttons (N and M).
The problem here is that in 2.3, key.disabletextentry() was called when
closing a script. However, #944 removed the call. Therefore, a
regression.
If you used command-line playtesting to load a level in a zip, the game
would print a warning saying the level wasn't found. This is because the
warning is printed when it tries to load a level before it loads zips,
inside the metadata load function itself.
To fix this, just move the responsibility for printing the error outside
the function, and put it on the caller.
This prints all binary blob check fails. It's an error if the game
rejects the header and will refuse to load it at all, and a warning if
the game continues on.
This also removes the EOF check (`offset + header->size > size`) as a
fatal error. It will only print a warning now. If the last header goes
past the end of file, it will be handled gracefully by PhysFS, which is
the same case in VVVVVV 2.2. This actually fixes a regression from 2.3
where certain custom level tracks that were working perfectly fine in
2.2 (e.g. Summer Spooktacular's track 15) refused to play since 2.3.
This checks the return value of PHYSFS_readBytes() in all cases, and
uses a wrapper to not duplicate common logic. If the read fails, then it
will vlog an error, else if the amount of bytes read was less than
expected, it will vlog a warning.
Additionally, the space allocated for a file in
FILESYSTEM_loadFileToMemory is SDL_calloc()ed instead of SDL_malloc()ed
so if there are less bytes than expected, the memory will at least be
zeroed. This also means we don't have to do the null termination,
because the last byte will already be zeroed.
The return value of PHYSFS_readBytes() when reading the headers of
binary blobs now assigns to `header->size`, so the call has been placed
after the increment to `offset` because `offset` needs the correct (i.e.
intended) size of the header.
In the past I was thinking we could use some kind of feature in VVVVVV
to track outdated strings across language files. But as it turns out, a
manual solution is actually *perfect* (in combination with automatic
syncing): duplicating strings and marking the old one as outdated. We
started doing it in recent PRs, so let's make it official by adding it
to the readme.
In Italian, "Credits" is "Riconoscimenti", which runs offscreen with
the 3x font size that this title uses in the rolling credits at the end
of the game. I'm not sure if the translators saw that specific
instance, or thought the limit complaint was about the main menu button
all along (which is more prominent and *does* stick out far enough that
the complaint could plausibly have been about that, from a translator's
perspective!)
Either way, it's solved now: this string's width is now checked, and if
it will run offscreen at 3x size, it will now be displayed at 2x size
instead. The limit has been increased from 13 to 20 in the language
files accordingly.
This already happened on 2023-03-16, but I held off on updating the
repo's version a bit long: I wanted to wait a little to batch it up
with the next update, but the next update only arrived today, so...
I noticed that in 2.3, the game would launch with default controller
binds upon first launch (e.g. no pre-existing unlock.vvv or
settings.vvv), but in 2.4, this wasn't the case, and the default binds
would only be set the next time the game was launched. This would result
in you being essentially unable to use the controller on first launch
save for the analogue stick and D-pad to move between menu selections
and move the player.
Bisecting pointed to commit 3ef5248db9 as
the cause of the regression. It turns out returning early upon error or
a file not existing didn't set the default controller binds, because
they were done at the end of Game::deserializesettings(). But the binds
would be set on the next launch because if the file didn't exist, a new
file would be written, not with the default binds, but then the next
launch would read the file, see there were no binds, and then set the
default binds accordingly.
To fix this, I made it so that the default controller binds are set when
Game is initialized. That way, it covers all cases where the game can't
read a settings file.
Ever since 2.3, if you bind a controller button to the "menu" action
(i.e. back/escape) you won't be able to change that button to any other
action, because pressing it anywhere in the binding menu will exit to
the previous menu, without applying the binding.
I know action sets will overhaul everything, but 2.4 may (probably
"should") come out before we have action sets. This part is very
broken, and the fix is very easy: just move the bind-assigning code to
above the menu-return-on-esc code, and add a return.
For some reason, the accessibility option that was meant to disable
flashes doesn't disable ALL flashes, only screen flashes and screen
shaking. This commit disables a lot more, most importantly randomness
in colors, the player flashing on death/respawn, and teleporters
flashing.
This updates the interpolation positions of the player when transforming
into and out of VVVVVV-Man.
Otherwise, it can be seen that the player "zips" quickly during these
transformations if the Secret Lab entrance cutscene is played with
screen effects off.
Unfortunately, 1de459d9b4 caused a
regression where musicclass::niceplay() didn't work, because fading out
the music would cause haltdasmusik() to be called, which would reset the
fade variables.
To fix this, haltdasmusik() will now only reset the fade variables if
it's not called from a fade, which is signaled with a function
parameter.
`platv` is a room property that controls platform speed, and it has
always worked (other than some weird storage issues due to a bug).
However, the editor has no way to edit it currently, so people had to
resort to editing the level file by hand, or with a third-party tool.
This commit simply adds an easy way to modify platform speed.
VED has a fill bucket subtool for tiles and backgrounds, which is
really useful when creating rooms. This commit adds a fill bucket as
well, with an adaptive tool highlight, unlike VED.
This fixes a regression from 2.3 where the very beginning of A New
Dimension isn't silent.
A New Dimension's level music is set to Predestined Fate, but there is a
script box the player touches right upon spawning that stops playing
music. Then after the player ascends upwards, they touch a script box
that plays Predestined Fate. But in 2.2 and before, the very beginning
is silent due to the script box that stops music.
However in 2.3, due to the changes made to playing music during a fade,
the initial level music trying to play Predestined Fate during a music
fadeout from the main menu resulted in Predestined Fate being stored in
the nice fade variables, which kicked in after halting the music since
halting didn't reset those variables.
This resets those variables whenever music is halted, and now the
beginning of A New Dimension is back to how it was in 2.2 and before.
This fixes a regression where the red channel 0 glitch didn't work,
because the surface was always whitened, because LoadSprites would
whiten the image before converting it to surface.
This regression happened because of #923.
Fixes#962.
Misa asked me if this should only work for non-transparent textboxes,
and it shouldn't - that was kind of an oversight.
To make it work for transparent textboxes as well, I made a little
restructuring to avoid duplicating the code - fill_buttons() is now
called textbox_line(), and it replaces the direct accessing of the
textbox lines in the printing loops. The code that checks the width
of the textbox does not need to be copied, since the text box is
naturally not drawn for transparent text boxes.
This makes it so that `CustomEntity`s, at least internally, do not use
global tile position. Instead, they will use room-x and room-y
coordinates, which will be separate from their x- and y- positions.
This makes it much easier to deal with `CustomEntity`s, because you
don't have to divide and modulo everywhere to use them.
Since editorclass::add_entity and editorclass::get_entity_at expect
global tile position in their arguments, I've added room-x and room-y
arguments to these functions too.
Of course, due to compatibility reasons, the XML files will still have
to use global tile position. For the same reason, warp token
destinations are still using global tile position too.
As reported by Lilithtreasure on the VVVVVV Discord server, it is
possible to get gray moving platforms and enemies in the main game.
This happens if you play the main game after loading a custom level with
a room that is gray at the same coordinates. E.g. if you play a custom
level with a gray room at (12, 4), then exit and go to Gantry and Dolly
in the main game which is also at (12, 4), then the platforms there
would be gray too.
This is because there is a missing map.custommode check.
The missing piece from sound effects was handling what to do when the
buffer ran out. Which seems to happen often when decoding from OGG,
unlike WAV. This handling involves callbacks to functions named
refillReserve and swapBuffers.
Without this code,some sound effects would be cut off early, as
documented in #953. This might also explain the division by 20 - which
I've copied too, just in case.
Now OGG sound effect tracks should be identical to music tracks (except
I've stripped the looping code out).
Fixes#953.
We were using this function to check if the format of the existing voice
is different from the format of the voice we intended to play. However,
whereas the format we use is the FAudioWaveFormatEx struct, the only
details we get from FAudioVoice_GetVoiceDetails is the
FAudioVoiceDetails struct, which has much less details and is missing
important things like whether the format is 8-bit or 16-bit or something
else.
And of course, if we don't check that the number of bits matches, then
it still leads to playback issues as described in #953.
There are no other functions in FAudio that operate on
FAudioWaveFormatEx structs. So instead, we'll just have to do it
ourselves, and store the format of the existing voice alongside the
format of the intended voice. And then use SDL_memcmp to make sure the
formats are the same before playing, and if not, then destroy and
re-create the voice.
Fixes#953.
There's a few places where textboxes are constructed through code, but
they pass in the color's RGB values in manually. This commit
unhardcodes most of them them, replacing them with a color lookup.
The ones that weren't changed are special cases, like `175, 174, 174`.
I thought 2.2 already had separate map and interact gamepad bindings,
and they simply got neglected and broken with 2.3's split Enter/E key
option. But actually, the new split Enter/E option also applied to
gamepad buttons, and a separate interact binding was added, without
really indicating anything if Enter and E are not split. And I guess
using the same button for map and interact by default also makes sense
for simplicity...
This commit makes sure the button glyph displayed in-game is at least
the correct button. The gamepad bindings menu is also slightly modified
to darken the interact option - the button glyphs code now
automatically causes them to show equal buttons anyway, so it wasn't
too big of a change to also darken the line and disable the binding
option. To me this says: "the interact key is fixed to be the same as
enter right now, but there is a way to change it."
It's still not ideal of course, and I know a similar change to the
gamepad menu to hide the interact option was rejected a year ago
because action sets would already fix it, but it's a year later now,
and showing misleading button glyphs should be fixed in 2.4, whether it
will already have action sets or not (And at this point I think the
plan already is to keep the existing input system for 2.4)
And it's a 3 line diff to darken and disable the option, compared to
fully hiding the option.
The language screen has a "Press Space, Z, or V to select" hint, which
I forgot to update for supporting button glyphs in #943, so this commit
does.
<action_hint>Press Space, Z, or V to select</action_hint>
<gamepad_hint>Press {button} to select</gamepad_hint>
> CMake is re-running because D:/a/VVVVVV/VVVVVV/desktop_version
> /build_default/CMakeFiles/generate.stamp is out-of-date.
> the file 'D:/a/VVVVVV/VVVVVV/desktop_version/CMakeLists.txt'
> is newer than 'D:/a/VVVVVV/VVVVVV/desktop_version/build_default
> /CMakeFiles/generate.stamp.depend'
> result='-1'
"newer" probably means "the last edit date is newer because it was just
cloned", so let's see if including it in the cache means the last edit
date will be kept the same too... We can freely do this, as the cache
key includes the hash of the file itself.
Since the initial `cmake -G "Visual Studio 17 2022" ..` seems to be a
real bottleneck on the Windows CI (sometimes taking multiple minutes
for seemingly no reason), cache the build folder based on the hash
of CMakeLists.txt. It should be possible to reuse the build folder if
CMakeLists hasn't changed, and if it does change (because someone adds
a source file or library or something), we'll just do that initial
cmake with -G once, and cache that version as well.
https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows
For some reason, this test takes a dozen seconds or more. Maybe because
it needs to boot up powershell or something like that? No idea, but
it's just sitting there. The cache action has a return value that
indicates whether there was a cache hit, so there's no need to use
Test-Path, saving CI time.
EDIT: Maybe the SDL cache hasn't ever been working. It seems to cache
30 bytes (huh?) and the folder it caches is C:\SDL, not C:\SDL2-2.24.0
as the folder would look like. So it was probably downloading SDL every
time anyway?
Also, I upgraded the cache@v2 action to cache@v3. I added a cache@v3
myself and noticed this was still using cache@v2, so I figured, why not
future-proof it? And that's when I notice this warning: "Node.js 12
actions are deprecated. Please update the following actions to use
Node.js 16: actions/cache@v2." So it's pretty good timing.
This was easier than I expected - just add an optional buttons="1"
attribute to cutscenes.xml. It's treated like the speaker attribute -
it's only there as context for the translator, and for the cutscene
test.
Due to rebasing messiness and diff noise, it's probably best if pull
requests either don't sync all the language files at all (and only
modify the English ones) OR only do it as a final commit. It's still
something we need to figure out, lol.
If a controller button is pressed, a controller is connected (even at
startup!) or an axis is moved, the game will switch to displaying
controller glyphs. If a keyboard key is pressed or the last controller
is removed, the game will switch to displaying keyboard keys.
This adds mappings from SDL's Xbox-based SDL_GameControllerButton
constants, to glyphs for the following layouts:
- LAYOUT_NINTENDO_SWITCH_PRO,
- LAYOUT_NINTENDO_SWITCH_JOYCON_L,
- LAYOUT_NINTENDO_SWITCH_JOYCON_R,
- LAYOUT_DECK,
- LAYOUT_PLAYSTATION,
- LAYOUT_XBOX,
- LAYOUT_GENERIC,
There may still be errors in these, but they should be mostly correct.
I'm leaving it up to Ethan to make it show the correct button glyphs
for the correct controllers being connected (and possibly to fix these
mappings where needed).
Violet's dialogue now looks like this:
squeak(purple)
text(purple,0,0,2)
Remember that you can press {b_map}
to check where you are on the map!
position(purple,above)
textbuttons()
speak_active
The new textbuttons() command sets the next textbox to replace {b_map}
with the map button, and {b_int} with the interact button. The
remaining keys would be added as soon as they need to be added to
ActionSets.h as well.
This adds a function that converts an action (such as interacting
in-game) to the corresponding button text ("ENTER", "E") or button
glyph (PlayStation triangle, Steam Deck Y, etc). This function
currently only gives the existing ENTERs or Es, because I don't know
how best to detect controller usage, or whether the game is running on
a Steam Deck, or what buttons need to be displayed there. Still, it
should now be really easy to adapt the rendering of keyboard keys to
consoles, controllers, or rebound keys.
To identify the actions that currently need to be displayed, this
commit also adds the initial enums for action sets as described by
Ethan in a comment in #834 (Jan 18, 2022).
These visualize the horizontal gravity line kludge for rooms beside
eachother. When you enter another room, gravity lines which look like
they're connected between the rooms try to have the same activated
state.
Basically, if you're in room (1,4) and you go into (2,4), if a
gravity line in (1,4) is activated (gray, on cooldown) and it's
touching the gravity line in (2,4), that gravity line will also be
activated.
This uses DDA (https://w.wiki/6RSQ) to draw a line between the previous
frame's mouse position, and the current frame's mouse position. This
means that there will no longer be gaps in lines of tiles if you move
your mouse fast enough (which is actually rather slow, so it gets
annoying quickly).
The editor's timestep is no longer hardcoded to 24, as I assume that
was only done so there would be less gaps in lines of tiles drawn.
With interpolation, that is no longer an issue, so I've removed the
editor's special case for the timestep.
Scripts used a weird "hook" system, where script names were extracted
into their own list. This was completely unneeded, so it has been
replaced with using the script.customscripts vector directly.
The script editor has been cleaned up, so the cursor's Y position is
relative to the entire script, rather than what's just displaying on
the screen currently. This simplifies a lot of code, and I don't know
why it was done the other way in the first place.
The script selector and script editor cursors have been sped up, since
both lists can be massive, and waiting 6 frames per line is extremely
slow and boring. This is still slow and boring, but we don't have
proper input repetition yet.
This commit moves everything left out of the previous commit to the
state system. This means a bunch of new functions were added as well,
to avoid the code in each function becoming too huge. A lot of cleanup
was done as well, simplifying logic, merging duplicated code, etc.
This commit does NOT touch "script hooks", script editor logic and
autotiling, as those seem to be their own separate beasts.
While warp lines were being drawn, they also got resized to
automatically fit between collision. In renderfixed, gravity lines are
resized the same way. Doing logic while drawing is very poor practice,
so resizing of these has been moved into logic, and merged together.
Aside from some more cleanup, this commit also removes the very poorly
done right click emulation, when you hold CTRL and click. It never
worked well in the past, and even requires a right click to use, so
there's not really any point to keeping it around.
The drawer could definitely be improved further, however I cleaned up a
little bit of the code duplication. I'll have to take a closer look
some other time, but I'm pretty sure that the duplicated code at the
bottom can be removed with a few tweaks, but I'll do that carefully
in a different commit.
Tools were a mess, spread all over the code with hundreds of `else-if`
statements. Instead of magic numbers denoting tools, an enum has been
created, and logic has been extracted into simple switch/cases, shared
logic being deduplicated.
The base of a state system for the editor has been created as well,
laying a good path for further organization improvements. Because of
this, the entire editor no longer gets drawn underneath the menus,
except for a few pieces which I haven't extracted yet. Either way,
this should be good for performance, if that was a concern.
I have this annoying issue where the game will open on the wrong monitor
in fullscreen mode, because that monitor is considered to be display 0,
whereas the primary monitor I want is display 1.
To mitigate this somewhat, the game now stores the display index that it
was closed on, and will save it to settings. Then the next time the game
opens, it will open on that display index. This should work pretty well
as long as you aren't changing your monitor setup constantly.
Of course, none of this applies if your window manager is busted. For
example, on GNOME Wayland, which is what I use, in windowed mode the
game will always open on the monitor the cursor is on, and it won't even
be centered in the monitor. But it works fine if I use XWayland via
SDL_VIDEODRIVER=x11.
Previously, the game would not store the size of the window itself, and
would always call SDL_GetRendererOutputSize() (via
Screen::GetWindowSize()) to figure out the size of the window. The only
problem is, this would return the size of the whole monitor if the game
was in fullscreen mode. And the only place where the original windowed
mode size was stored would be in SDL itself, but that wouldn't persist
after the game was closed.
So, if you exited the game while in fullscreen mode, then your window
size would get set to the size of your monitor (1920 by 1080 in my
case). Then when you opened the game and toggled fullscreen off, it
would go back to the default window size, which is 640 by 480.
This is made worse, however, if you were in forced fullscreen mode when
you previously exited the game in windowed mode. In that case, the game
saves the size of 1920 by 1080, but doesn't save that you were in
fullscreen mode, so opening the game not in forced fullscreen mode would
result in you having a 1920 by 1080 window, but in windowed mode.
Meaning that not even fullscreening and unfullscreening would put the
game window back to normal size.
The solution, of course, is to just store the window size ourselves,
like any other screen setting, and only use GetWindowSize() if needed.
And just to make things clear, I've also renamed the GetWindowSize()
function to GetScreenSize(), because if it was named "window" it could
lead one to think that it would always return the size of the screen in
windowed mode, when in fact it returns the size of the screen whatever
mode it is in - fullscreen size if in fullscreen mode and window size if
in windowed mode.
And doing this also fixes the FIXME above Screen::isForcedFullscreen().
This fixes the following bug that only occurs on Wayland: If the game is
configured to be fullscreened and in stretch mode, on startup, it won't
be in stretch mode. It will appear to be in letterbox mode, but the game
still thinks it's in stretch mode.
This is because during the ResizeScreen() call on startup, for whatever
reason, the window size will be reported to be the default size (640 by
480) instead of the screen resolution of the monitor, as one would
expect from being in fullscreen. It seems like when the game queries the
window size, the window isn't actually in fullscreen at that time, even
though this is after fullscreen has been set to true.
To fix this, I decided to always update the logical size before
SDL_RenderPresent() is called. To make this neater, I put the scaling
code in its own function named UpdateScaling().
This bug has existed since 2.3 and does not occur on X11. I tested this
on GNOME Wayland, and for testing it on X11, I used Openbox in a Xephyr
session while running VVVVVV with SDL_VIDEODRIVER=x11.
It turns out this texture is only used as a temporary texture to draw
the screen with an offset before rendering it to the output target.
I thought it was used for drawing the map menu animation, but that was
only true of `tempBuffer`, and is no longer true of the new render
system.
When `blackout` is active, the screen (to simplify) stops getting drawn
to. The excecption is textboxes, which draw anyway. But since the
screen isn't being cleared, removed textboxes stay on screen, since
that texture isn't being cleared. In the SDL_Renderer PR, I
accidentally broke this behavior, so this commit fixes it.
Fixes#951.
Dav999 pointed out this potential issue on Discord.
While basically all memcmp implementations will terminate early here if
there's a null byte (because it's mismatched), it doesn't hurt to add
the check here.
This fixes a bug where the trinket collection text boxes, along with the
cutscene bars, would stay on-screen if the player warped to the ship
while they were up.
This only happens during the gamestate 0 anti-softlock checks, and only
if completestop is active in the first place, so text boxes aren't
cleared if the player is doing something that wouldn't lead to a
softlock otherise.
Fixes#921.
This fixes a regression where the behavior of duplicate player entities
is different, causing a gameplay section in Vespera Scientifica to be
impossible, as described in #903.
In the level, you are allowed to flip in mid-air. Vespera accomplishes
this by having two duplicate player entities stuck in a platform. One of
them is responsible for letting the player flip in one direction, and
one of them is responsible for letting them flip in the other.
In 2.3, this works because in order for a player entity to flip,
`game.jumppressed` is checked, and the entity will flip if
`game.jumppressed` is greater than 0, then `game.jumppressed` will be
set to 0. In this way, whenever a player entity flips, it will set
`game.jumppressed` to 0, so whenever the next player entity is
evaluated, `game.jumppressed` is 0 and thus _that_ entity will not flip.
This is because the for-loop surrounds both the `game.jumppressed` check
and flipping the entity. In 2.4 after #609 and subsequent patches,
however, this is not the case. Here, the for-loop only surrounds
flipping the entity. Therefore, the `game.jumppressed` check is
evaluated only once. So, it will end up flipping every player entity if
the entities are eligible. In this case, one of them is eligible twice,
resulting in the game flipping gravitycontrol four times, which is
essentially the same as not flipping at all (except for all the sound
effects).
Hence, the fix here is to make it so the for-loop surrounds the
`game.jumppressed` check.
Now, this doesn't mean that the intent of #609 - that duplicate player
entities have the same initial velocity applied to them when flipping -
has to be removed. We can just put the for-loops back in. But I
carefully implemented them in such a way that the overall function is
not quadratic, i.e. O(n²). Instead, there's a pass done over the
`obj.entities` vector beforehand to store all indexes of a player entity
in a temporary vector, and then that vector is used to update all the
player entities. In this manner, the function is still linear - O(n) -
over the number of entities in the room.
I tested this to make sure that no previous regressions popped up again,
including #839, #855, and #887. And #484 is still resolved.
Fixes#903.
This adds support for OGG files as sound effects (via renaming them to
the wrong .wav file extension), because in previous versions of the
game, SDL_mixer didn't care what the file extension was, and so some
people relied on this, as described in #900.
This is accomplished by copy-pasting the OGG loading code for music
tracks. For a bit of cleanliness, I put the WAV and OGG loading code in
separate functions.
This is mostly the same code, except that because sound effects don't
loop and can't be paused or resumed, there's no reserve buffer, and
there's no data for loop points.
Also, for some reason, the music loading code divided by 20 in the
`size` calculation. I found that this prematurely cut off sound effects,
and that it made more sense to just not do the division. I don't know
why it was there, but removing it works.
Also also, some OGG files don't work with this. Namely, ones produced by
FFmpeg. To test this, I just extracted 0levelcomplete.ogg from
vvvvvvmusic.vvv and replaced terminal.wav with it. And it works, so
hopefully I won't have to touch audio code again, although I might if
someone complains about this. But either way, I'm committing this
because it's better than it was before.
Fixes#900.
I noticed that this call wasn't using VVV_freefunc. I missed it earlier
when going through Music.cpp and checking for instances when
VVV_freefunc should have been used
(a926ce9851).
Sound effects already get recreated if the number of channels
mismatches, but the same could be true if the sample rate mismatches
too, which was the case with music tracks as described in #886.
So, just to be sure - and to be consistent with music tracks - sound
effects now check that the sample rate matches, too, and if not, will be
recreated.
This fixes an issue where sound effects of bit depths that weren't 16,
such as 8, were being played incorrectly, as described in #886.
The problem is that when loading the sound effect, we would always set
the bit depth to 16 no matter what! Instead, we should set the bit depth
to the actual bit depth of the file.
Fixes#886.
As described in #886, if a track was played when an existing track was
already playing, and the sample rates of the two tracks differ, then the
second track would play wrong and distorted.
This is because the second track would play with the sample rate of the
first. To fix this, halt the track if the sample rate is mismatched,
which destroys the voice. This results in the voice being recreated
later in the Play() function. The track is also halted if the number of
channels or bit depth is mismatched.
Fixes#886.
The style we have here is that functions with no arguments are to have
explicit `void` arguments, even though this doesn't apply in C++,
because it does apply in C.
Unfortunately, some have slipped through the cracks. This commit fixes
them.
This removes the `addnull` argument from `FILESYSTEM_loadFileToMemory`
and `FILESYSTEM_loadAssetToMemory`, and makes it so a null terminator is
always appended no matter what.
This simplifies things and removes the need for callers to make the
decision about null termination and what its implications are. Then you
get cases where null termination might not happen when it should be,
such as the one df577c59ef (#947) fixed.
When FIQ added the `addnull` argument in
5862af4445 (#117), I'm guessing he did it
because he wanted to be cautious about adding the null terminator to
every file, so he only did it for XML files, which was the only case
needed at the time. But really, there's no downsides to always appending
a null terminator. In fact, it's already always done whenever the STDIN
buffer is loaded.
Because of how `blackout` works, screen shaking must clear the gameplay
buffer. `blackout` simply pauses rendering, so if the gameplay buffer
gets cleared, then the screen will just be black, otherwise it'll look
like the game is "frozen". VVVVVV only uses `blackout` during screen
shaking, so it works as intended. However, when reimplementing this
behavior in the move to using the SDL_Renderer system, I failed to
notice that since my implementation always clears the gameplay buffer
when shaking, if you open the menu during a shake, instead of seeing
gameplay during the transition animation, you only see black. This has
been fixed with a simple `game.blackout` check before clearing the
gameplay buffer.
For some reason, originally, this function mutated the std::string
passed into it by reference. So calling the function could potentially
mutate whatever got passed in, and callers potentially could have relied
on that behavior.
Now that the surrounding callsites have all been cleaned up, though
(especially scriptclass::startgamemode), it's clear that it's only used
in two places: Loading the level in the editor, and loading the level in
live gameplay. In both cases, the passed-in string isn't used ever again
afterwards.
So, it's safe to delete the mutable reference without any undesirable
effects, making the code cleaner and easier to understand. The function
now mutates a copy instead of mutating whatever the caller has.
An example is Maximally Misleading Miserable Misadventure, which has a
font.txt which includes all ASCII characters starting with a 0x00 byte.
This would accidentally null-terminate the string too early.
Instead, we now use the total length of the file again, and keep
getting the next UTF-8 codepoint until the file ends. We still need to
null-terminate it - it protects against incomplete sequences getting
the UTF-8 decoder to read out of bounds.
This was an oversight when we migrated to the new UTF-8 system - it
expects a null-terminated string, but the utfcpp implementation worked
with a pointer to the end of the file instead.
I also added an assert in FILESYSTEM_loadFileToMemory() so this is less
likely to happen again - because there should be no valid reason to
have a NULL pointer for the total file size, as well as not wanting a
null terminator to be added at the end of the file.
Courtesy of Reese. Mainly the accented characters are updated -
uppercase letters are now mostly a pixel higher to make them higher
than lowercase letters (and only a single pixel lower than uppercase
letters without accents, instead of two pixels lower). Accents for
lowercase letters have also been made thicker overall and changed in
appearance.
Also: this font image is converted to indexed grayscale instead of full
RGBA, which makes the file only 4.5 KB instead of about 10 KB.
This updates all language files to the latest version.
- Some minor errors are also fixed - for example, a small number of
changes were made to the English string instead of the translation.
Alignment of the dimensional stability generator terminal is also
improved in several languages.
- I also discovered that the string "Complete the game" appears twice -
and has, to be consistent with adjacent strings, two separate
translations in Portuguese (PT). So this string now properly has two
different cases so it can be translated separately.
- The limit for TIME/SHINY/LIVES has been bumped from 7 to 8
The following languages are new:
- French
- German
- Italian
- Portuguese (BR)
- Portuguese (PT)
- Russian
- Spanish
- Turkish
Esperanto has also received some updates.
`hardestroom` currently stores the current roomname, but it was missing
a call to get the translated string. This has been added, fixing the
hardest room appearing as untranslated.
See the previous two commits, a lot of the time we don't need
std::string objects to be passed to these functions because we already
have C strings.
Commit 1/3: font::print_wrap
Commit 2/3: font::print
-> Commit 3/3: font::len
Turns out I was overplaying my hand a little when changing font::print
from std::string to const char*, so instead, I'll overload the
function: it can take either a const char* (the main function) or a
std::string (a wrapper). This means any C string that's printed
everywhere else (which is common, especially because loc::gettext gives
them) no longer needs to be converted to a std::string object each call.
Commit 1/3: font::print_wrap
-> Commit 2/3: font::print
Commit 3/3: font::len
We no longer need to pass a std::string object to the print and len
functions - in fact, we often only have a C string that we want to
print or get the visual width of (that C string most often comes from
loc::gettext), and it's a bit wasteful to wrap it in a new std::string
object on every print/len call.
This does mean adding a few more .c_str()s, but there's not many places
where a std::string is being passed to these functions, and we already
use .c_str() sometimes.
-> Commit 1/3: font::print_wrap
Commit 2/3: font::print
Commit 3/3: font::len
This removes memory churn caused by using analogue mode.
The surfaces are only allocated if analogue mode is turned on, and kept
after they are initialized. Otherwise, if analogue mode is never turned
on (which will be the case for the vast majority of the time the game is
played), then no extra memory is used.
Drawing a texture onto itself seems to produce issues on Metal.
To fix this, use a temporary texture instead, that then gets drawn onto
the original texture.
Fixes#927.
These are from a fan translation that was originally made in 2020.
The files were kept around as a possible base for the future Spanish
translators, and now that a first version of the new Spanish
translation is being tested, it's time to remove this one. These files
are making it very annoying for me to test all the new translations and
then jump around between different commits (and they're already not in
the round 2 translator pack).
Actually, this won't really help with the jumping around different
commits part... But the sooner it's removed, the less confusing it will
be when different versions of the language pack are floating around and
the latest version needs to be added, and the less "Changes not staged
for commit" problems you'll get when testing the new language packs.
(Or two different españols being on the language screen)
This is because destroying the renderer causes use-after-frees since the
renderer destroys all textures when it gets destroyed.
This fixes a Valgrind error where an invalid read occurs because the
font textures get destroyed again after the renderer is destroyed.
The hashmap would get populated with the name of each font, as each
font was being added. Unfortunately, adding a font would also realloc
the storage for fonts, in which the names are also stored... Possibly
invalidating the pointers to the names. This is now fixed by populating
the hashmap after all the fonts are added.
For consistency, since they are created in create_buffers as well. I
checked with Valgrind (which is very noisy on Wayland, it turns out),
but I didn't see anything about them not being freed. It doesn't hurt to
use VVV_freefunc here anyway, though, since it does a NULL check and
nulls the pointer afterwards, which should prevent double-freeing and
use-after-frees.
I'm going to soon be creating an actually temporary texture, so having
two textures named "temp" would get confusing. This is also a good
chance to correct the name of this texture, because it's not really
temporary, but it's used for map menu animation rendering.
This commit replaces the old system with the new one, making it much
easier to edit the transforming and glitchy roomnames. Additionally,
this syncs flag 72 to finalstretch.
Co-authored-by: Misa Elizabeth Kai <infoteddy@infoteddy.info>
This commit adds a better system for animated roomnames.
The old system, like many other systems, were very hardcoded, and can be
described as mostly else-if chains, with some fun string comparisons.
The new system uses lists of text for transformations and glitchy names,
making it much easier to add new cases if needeed.
This commit implements the system but does not replace the old system,
where that is done in the next commit.
The settings for special roomnames can be read from level XML, and
`setroomname()` can be used from commands to set a new, static name.
I'm also planning to change the argument types of font::len,
font::print and font::print_wrap from const std::string&s to
const char*s, but I'll do that separately.
This is a small library I wrote to handle UTF-8.
Usage is meant to be as simple as possible - see for example decoding
a UTF-8 string:
const char* str = "asdf";
uint32_t codepoint;
while ((codepoint = UTF8_next(&str)))
{
// you have a codepoint congrats
}
Or encoding a single codepoint to add it to a string:
std::string result;
result.append(UTF8_encode(0x1234).bytes);
There are some other functions (UTF8_total_codepoints() to get the
total number of codepoints in a string, UTF8_backspace() to get the
length of a string after backspacing one character, and
UTF8_peek_next() as a slightly less fancy version of UTF8_next()), but
more functions could always be added if we need them.
This will allow us to replace utfcpp (utf8::unchecked) and also fix
some less-than-ideal code:
- Some places have to resort to ignoring UTF-8 (next_wrap) or using
UCS-4→UTF-8 functions (VFormat had to use PHYSFS ones, and one other
place has four lines of code including a std::back_inserter just for
one character)
- The iterator stuff is kinda confusing and verbose anyway
Originally the changedir command was used here, making
Vitellary look left and then immediately snap back to
looking right. Now the changeai command is used instead
to make him actually look left, and then look back to
the right on his last textbox.
The `-addresses` command-line option added in 64be99d4 helps
autosplitters on platforms where VVVVVV is not built as a
position-independent executable. macOS has made it increasingly
difficult, or impossible, to build binaries without PIE.
Adding an obvious string to search for will help tools that need to deal
with versions of VVVVVV built with PIE. The bytestring to search for is
`[vVvVvV]game`, followed by four null bytes (to avoid finding it in the
program code section). This identifies the beginning of the game object;
addresses to other objects can be figured out by relative offsets
printed by `-addresses`, since ASLR can only change where the globals
begin.
Partially fixes#928; it may still be advisable to figure out how to
explicitly disable PIE on Windows/Linux.
Activity zone prompts have always been limited to a single line,
because the text box had a hardcoded size. A translator requested for
the possibility to add a subtitle under music names for the jukebox,
and the easiest solution is to make it possible to translate a prompt
with multiple lines. This is possible now, and the textbox even
wordwraps automatically.
(I wouldn't really like to see translations using multiple lines for
stuff like "Press ENTER to talk to Vitellary", especially if it wraps
with one name and not with another, but if a string is too long,
wordwrapping will look better than text running out of the box.)
There were two print calls, one for the transparent case, and one for
a regular textbox. The print calls were nearly the same except for the
color, and for some reason the transparent case didn't have PR_CJK_LOW
(that one is on me).
There is no overlap in side effects between this line and the switch
statement after it, but it did result in adding the width of a final
null terminator or newline to the width of the current line, which is
a waste because those widths both 1) require trying to find
non-existent characters in the font and 2) will not be used.
I found this out because I added a debug print in find_glyphinfo(), and
something was requesting lots of codepoint 0 from the font.
In a button glyph font (like buttons_8x8.fontmeta) you can now specify
<type>buttons</type> to indicate that it's a button glyphs font. In a
normal font, you can specify <fallback>buttons_8x8</fallback>. This
will make it such that if a character is not found in the main font,
it will instead be looked for in buttons_8x8. If not found there
either, the main font's U+FFFD or '?' will be used as before.
This makes find_font_by_name() not O(n). It's not really a big deal,
because there won't be many fonts, but it'd make a function in the next
commit (finding the given fallback font for each font by name) O(n^2).
It's easy enough to add the hashmap.
They are not used yet in this commit - this just adds the graphics and
data for the glyphs. It also adds a <fallback> tag to font.fontmeta to
use buttons_8x8.
The icons themselves are made by Reese Rivers - see #859.
This makes them stand out more.
The border around the tool has also been moved to be drawn first.
Otherwise, it would be drawn on top of the outline of the text, which
would look bad.
This prints the address of every global class to the console, and then
exits.
This is useful for autosplitters, which read memory addresses directly.
Any time a new version of the game is shipped, this makes updating the
autosplitters much easier as people don't have to find the addresses of
the global classes themselves.
After discussing with Ally and Dav, we came to the agreement that this
is basically useless since the prompt will always be centered and take
up most of the horizontal space of the screen.
And the x-position was only added as an offset because at some point,
there was a missing space from the side of the "- Press ENTER to
Teleport -" prompt, and the offset was there so people could mimic the
prompt accordingly. But that was fixed at some point, so it's useless
now.
Even though it would be a bizarre combination, declaring no character
set (neither via <chars> nor via font.txt) meant that <special>
couldn't be used because the ASCII fallback charset would be loaded
after special ranges were processed. Now, all the methods of loading
the charset are attempted sequentially, and only afterwards, the
special ranges are loaded.
There used to be two ways of fading in/out text in VVVVVV:
- Local code that modifies the R, G and B values of the text
- Keeping the RGB values the same and using the alpha channel
The latter approach is only used once, for [Press ENTER to return to
editor]. The former approach causes problems with colored (button)
glyphs: there's no way for the print function to tell from the RGB
values whether a color is "full Viridian-cyan" or "Viridian-cyan faded
out 50%", so I added the flag PR_COLORGLYPH_BRI(value) to tell the
print function that the color brightness is reduced to match the
brightness of colored glyphs to the brightness of the rest of the text.
However, there were already plans to make the single use of alpha
consistent with the rest of the game and the style, so PR_ALPHA(value)
could be removed, as well as the bit signifying whether the brightness
or alpha value is used. For the editor text, I simply copied the "Press
{button} to teleport" behavior of hiding the text completely if it
becomes darker than 100/255.
Another simplification is to make the print function handle not just
the brightness of the color glyphs while local code handled the
brightness of the normal text color, but to make the print function
handle both. That way, the callsite can simply pass in the full colors
and the brightness flag, and the flag name can be made a lot simpler as
well: PR_BRIGHTNESS(value).
"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.
If your language has a bigger font, the max attribute isn't really
helpful to you as a translator, so the sync feature adds a special
max_local attribute which is accurate to the font size. This was
already documented in advance.
If used, we now also write an attribute in the root tag of strings.xml
and strings_plural.xml, that looks like max_local_for="12x12". I
decided to add this attribute after finding out the Excel macros would
be really hard to change to only show a max_local column if it is ever
used (it'd need to look ahead through the strings until it finds a
string with a max, or remove the column if no string has used it), but
it's also a convenience for translators.
If, somehow, a single character is wider than the limit, next_wrap
would get you stuck in an infinite loop by refusing to update the start
index and giving a line length of 0. Now, it just gives you a line with
that single character.
I also made some small readability improvements: I renamed next_wrap_s
to next_wrap_buf, and added comments at the top of both functions
explaining what they do.
By default, when you open the level editor to start a new level, the
level font will now match your VVVVVV language; so if you're, say,
Japanese, then you can make Japanese levels from the get-go. If you
want to make levels for a different target audience, you can change the
font via a new menu (map settings > change description > change font).
The game will remember this choice and it will become the new initial
level font.
If a custom level doesn't specify a font, it should be the 8x8 font.
But the main game can't specify a font, it's just the interface font
because that's for the language that the game is in.
They need to know how wide the text is going to be in a particular
font, so font::string_wordwrap and font::string_wordwrap_balanced now
take a flags argument like all the printing and dimensions-getting
functions. next_wrap and next_wrap_s take a Font* now, they're internal
to Font.cpp so they can take a Font and avoid double flag-parsing. But
if any non-Font.cpp code needs next_wrap/next_wrap_s in the future, I'd
just make a public wrapper that takes a uint32_t flags and passes the
Font* to the internal functions.
This is one of the few places still using 2-space indentation instead
of 4 spaces, and it makes it very annoying to work with when your tab
key inserts four spaces - so I could either just mimic it for the time
being, or I could just fix it while I was at it.
- If the level font is higher than 10 pixels, the third description
line (Desc3) is disabled and unavailable. CJK languages require less
characters to convey the same message (140 characters caused people
to cram tweets in all languages except CJK) and this gives us enough
room in the levels list without having to cram the metadata even more
than it already was or showing less levels per page.
- The "Untitled Level" and "by Unknown" now selectively show up in the
interface font instead of the level font.
None of the structs in the new font system ended up being "publicly"
accessible, they were all treated as implementation details for
Font.cpp to use, so these structs are now fully defined in Font.cpp
only.
The last two deprecated functions are:
- Graphics::Print
- Graphics::PrintWrap
These are used a lot, but they're relatively easy to replace, since the
only flag I probably have to immediately worry about is PR_CEN. I do
often need to add PR_FONT_* flags but I don't need to add any
PR_2X/PR_3X/PR_4X anymore.
Only three deprecated functions remain:
- Graphics::Print
- Graphics::PrintWrap
- Graphics::bigprint
I also fixed multiline transparent textboxes having their outlines
overlap the text itself, and fixed textboxclass::padtowidth assuming
glyph widths of 8 (it made the hints at the start of intermission 1
run offscreen for example)
Only four deprecated functions remain:
- Graphics::Print
- Graphics::PrintWrap
- Graphics::bigprint
- Graphics::bprint
Graphics::bprint is the least-used one of them, and after that, the
other functions are used a LOT, but it'll be a lot faster to go through
them, since I have less and less flags to worry about. I'll probably
start using Vim macros again like I did for loc::gettext()ing strings,
or maybe I'll automate this completely.
I migrated all of them to font::print, so they can now be removed.
Six deprecated print functions left! (Of which some are used a whole
lot, it's simpler if the lesser-used ones are gone first.)
This used some constants counting numbers of characters
(LEN_INTERIM_COMMIT and LEN_BRANCH_NAME) and even an SDL_arraysize,
all multiplied by 8, to get the length of the displayed text.
Now it just uses the new PR_RIGHT flag of font::print.
I did also force the 8x8 font for all the interim information, since
the date kinda overlapped with the menu options... And all of these
lines only show up in interim versions anyway, except for the version
number - which is left in the interface font for consistency with the
rest of the menu in non-interim versions. The inconsistency in interim
versions doesn't really matter all that much I think, it's just some
technical/debug info.
I migrated all graphics.len calls over to font::len (and also migrated
prints mainly surrounding those graphics.len's) so the old len function
is now completely removed.
I especially focused on graphics.len and the print calls around them,
because graphics.len calls appear a bit less often, might be overlooked
when migrating print calls (thus possibly using different fonts by
accident) and are often used for some kind of right-alignment or
centering which can be changed into PR_RIGHT or PR_CEN with a different
X anyway.
Notably, I also added a new function to generate these kinds of
sliders: ....[]............
Different languages means that the slider for analogue stick
sensitivity needs to be longer to fit possibly long words for
Low/Medium/High, and then different font sizes means that the longer
slider won't fit onscreen in a language that needs a 12-wide font. So
slider_get() can take a "target width", which dynamically changes the
number of characters depending on the width of them in the interface
font.
I kinda forgot that I could force the 8x8 font instead of adapting the
characters in the slider to the font, and other ideas (like using
different characters or a more graphical progress bar) have been
brought up on Discord, so this might all change again sooner or later.
If you for example have your VVVVVV set to English, have the option for
Chinese selected, and then the window loses focus, the English pause
screen would show up in the Chinese font. This is now fixed.
meta.xml can now have a <font> tag, which gives the name of the font
that the language needs. This will directly control the interface font
when the language is active, and will soon also control the font used
for each option on the language screen.
Also added some borders to more of the text in room name translator
mode, fixed a positioning issue if the interface font is not 8x8, and
migrated the trophy texts to font::print_wrap (including
PR_COLORGLYPH_BRI that still needed to be done)
Activity zones need to be in the interface font if the message is from
the system (like Press ENTER to activate terminal, which may be in a
different language) and in the level font if it's a customized message
(setactivitytext).
Graphics::drawtextbox was counting the textbox width and height in
8x8 characters, even including the borders as characters, so it'd need
to be told what the font for the textbox is, and then probably only the
height needs to be adapted to the font and not the width because that's
adapted to the screen width... So just call Graphics::drawpixeltextbox
directly instead. It was already weird enough how actual cutscene
textboxes called Graphics::drawtextbox with x/8, y/8 before the
previous commit, (when you already have the pixel width and height!)
only to have that be a wrapper for drawpixeltextbox by doing x*8, y*8.
Some textboxes need to be in the level font (like room names, cutscene
dialogue, etc - even in the main game), and some need to be in the
interface font (like when you collect a shiny trinket or crewmate). So
most of these textboxes now have graphics.textboxprintflags(font_flag)
as appropriate.
RoomnameTranslator.cpp is now also migrated to the new print system -
in room name translator mode, the room name is now displayed in the 8x8
font if it's untranslated and the level font if it is.
Level text such as room names, text box content, and the contents of
the script editor need to be displayed in the level-specific font, and
tweaked to look right. This involves displaying less lines in the
script editor, making text boxes bigger, displaying some text higher
and some text lower. This is still unfinished, but it's the real start
of a migration to font::print functions!
find_font_by_name() just finds the index of a given font name. This
index is supposed to be stored and reused, because the font (for a
language/level) won't be changed very often. So this function would
only run when getting the language metadata, when loading a level, etc.
All global fonts and all custom fonts in a level are now loaded, and
added to their respective "vectors". The selected font is still always
as the global font.png, and the custom level font also isn't selected
yet, but it's now easier to implement that.
Also, I added FILESYSTEM_enumerateAssets, which #902 already has but I
needed it now. I also rewrote it to not use std::vector<std::string>.
That was my idea, it's also how FILESYSTEM_getLanguageCodes worked,
so for symmetry, that function is getting changed as well.
The font::len function now handles the printing scale, so it can
immediately return the scaled length instead of having the caller
calculate it. The print function now handles CJK low/high flags and
vertically centers CJK text by default (instead of letting it stick
out on the bottom).
The following functions were moved directly:
- next_wrap
- next_wrap_s
- string_wordwrap
- string_wordwrap_balanced
- string_unwordwrap
These ones will probably still need get a flags argument, except for
string_unwordwrap (since they need to know what font we're talking
about.
The implementation of graphics.len has also been moved to Font.cpp,
but graphics.len still exists for now and is deprecated.
graphics.PrintWrap is now also deprecated. An advantage of the new
version (with flags) is that it'll be possible to do things like put
a border around wrapped text, wrap text at larger scales, etc, but
these things don't work perfectly yet.
This commit also has some other fixes, like the default advance of
6 pixels for characters 0x00-0x1F in 8x8 fonts.
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).
Since there's now a new XML-based font metadata file format to obsolete
the .txt file with all the glyphs, this commit switches the built-in
font to that new format.
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.
The MAKEANDPLAY, NO_CUSTOM_LEVELS, and NO_EDITOR defines remove content
or features. However, they then raise several warnings because of some
cases, functions, or variables that end up not being used.
This silences them by using the UNUSED macro, or by adding a default
catch-all case if the define is defined (so unhandled cases will still
raise warnings in a build that doesn't have these defines).
We get these warnings because of the typedefs for 64-bit integers in
PhysFS's header files. The compiler will treat them as extensions and
will still compile it fine but it does mean we aren't strictly standards
conforming. Which really isn't a problem anyway. Probably.
This adds the build type in brackets in `-version` output after e.g.
"VVVVVV v2.4". The build type is MAKEANDPLAY, NO_CUSTOM_LEVELS, or
NO_EDITOR (which are not necessarily mutually exclusive).
This is appended on to the end of the first line so as to not break
Ved's existing `-version` check which only checks if the beginning of
STDOUT is "VVVVVV" followed by any version number.
This makes it so that whenever the game loads a script as directed by a
script command, it will first try to load the script from the processed
argument, and if that fails only then will it try to load the script
from the raw argument.
This fixes a regression reported by Dav999 in the custom level "Vungeon"
created by Dynaboom, where a script `ifflag`s to `aselectP1.1` even
though the actual script name is `aselectp1.1`. In 2.3, it would
lowercase `aselectP1.1` and load the script properly, but previous to
this commit it would try to load the script with a capital name and then
fail.
This aborts and prints the error from SDL_GetError() if
SDL_CreateWindow() or SDL_CreateRenderer() fails.
We abort because there's not much point in continuing if the window or
renderer can't be created. There might be a use case for running the
game in headless mode, but let's code that in explicitly if we ever want
it.
This sets the minimum window size (to 320 x 240), so that the window
cannot be resized to lower than that.
This is because there's no utility in having a game window smaller than
that, and it provides a bonus convenience of being able to resize the
game to exactly 320x240 without needing to be exactly precise about it.
This idea was suggested by Dav999.
The `point` struct was a relic of ActionScript and was added because of
the Flash 'point' object. However, it seems like Simon Roth didn't
realize that SDL has its own point struct.
With this, `Maths.h` can be un-included from a couple headers, which
exposes the fact that `preloader.cpp` was relying on `Maths.h` being
transitively included from `Graphics.h`.
Dav999 added `#include "Localization.h"` before `#include "KeyPoll.h"`,
when it should go after instead, because the letter L comes after the
letter K in the English alphabet.
Ever since VVVVVV was initially ported to C++ in 2.0, it has used surfaces from SDL. The downside is, that's all software rendering. This commit moves most things off of surfaces, and all into GPU, by using textures and SDL_Renderer.
Pixel-perfect collision has been kept by keeping a copy of sprites as surfaces. There's plans for pixel-perfect collision to use masks instead of reading pixel data directly, but that's out of scope for this commit.
- `graphics.reloadresources()` is now called later in `main`, because textures cannot be created without a renderer.
- This commit also removes a bunch of surface functions which are no longer needed.
- This also recaches target textures in certain places for d3d9.
- graphics.images was converted to a fixed-size array.
- fillbox and fillboxabs use SDL_RenderDrawRect instead of drawing an outline using four filled rectangles
- Update my name in the credits
The pixel border around the map fits to map size normally. However, when
the map is off, it's always the dimensions of the full size map, and the
border didn't reflect that, so if the custom minimap was off, and the
map wasn't the full size, it wouldn't fit correctly.
This bug was introduced in PR #898.
The branch name will be added to the window title if it is an interim
version, e.g. "VVVVVV [master]".
This makes it easier for developers to tell at a glance which build of
the game they're running.
While the window is initialized with 640x480, the screen settings
defaulted to 320x240, which is a tiny window. The screen settings take
priority over the initialized window, so people with no previous
settings file will get 320x240. This makes it so they get 640x480
instead.
The window is still initialized to 640x480 (constants used for clarity)
just in case there's some weirdness if it's initialized to a potentially
odd resolution from the user's settings file.
This is useful for developers who may have multiple builds of the game
from various different branches and may easily forget which build of the
game is what.
This shows up in the bottom-right corner of the title screen and also
with the `-version` command-line option, and in the status message
printed when building the game.
Unfortunately there needs to be an intermediate surface for proper alpha
color blending to happen via SDL_BlitSurface. The exact SDL blending
logic seems complicated and unclear for me to implement at the moment,
and my attempts kind of failed, so this is just a stopgap measure to at
least get the game rendering how it was before I screwed it up.
This refactors them to not allocate a temporary surface by instead
simply drawing directly to the destination surface.
This means re-implementing the original semantics of SDL_BlitSurface in
them, which is the function signature they (and BlitSurfaceStandard)
were based off of. So now if src_rect or dest_rect are NULL, it
automatically uses a rect of the entirety of the corresponding surface,
and other things like that. And also some other optimizations like
no-opping if the alpha is 0 (because then nothing will be drawn), and
critical checks (not drawing if the destination pixel is out of bounds,
because then otherwise it would wrap around, or at least that's what it
did when I tested it).
This resulted in a bunch of code that would really suck to copy-paste
because then you'd have to remember to update the other copy, so I
refactored them further and put the common code into one function, while
separating the different code (the exact transformation they do) into
different functions that get passed in through function pointers.
In general, "temp" is a bad name because it could mean anything. In this
case the buffer isn't really temporary and it's only used for drawing
the menu with a certain offset, so I made it use a better name. But also
because I'm going to be adding temporary buffers so I don't want the
names to be confused.
Honestly not too sure why we ever wrote the mask handling logic
ourselves instead of using SDL functions. Hell, we even used SDL_MapRGB
for Graphics::getRGB before.
`ct` was used to be a variable that a color was temporarily stored in
before being passed to a draw function. But this is unnecessary and you
might as well just have a temporary of the color directly. I guess this
was the practice used because temporaries were apparently really bad in
Flash.
setcolreal() was added in 2.3 to do basically the same thing (set it
directly from entities' realcol attributes). But it's no longer needed.
Correspondingly, Graphics::setcol has been renamed to Graphics::getcol
and now returns an SDL_Color, and Graphics::huetilesetcol has been
renamed to Graphics::huetilegetcol for the same reason.
Some functions (notably Graphics::drawimagecol and
Graphics::drawhuetile) were relying on the `ct` to be implicitly set and
weren't ever having it passed in directly. They have been corrected
accordingly.
colourTransform is a struct with only one member, a Uint32. The issue
with `Uint32`s is that it requires a bunch of bit shifting logic to edit
the colors. The issue with bit shifting logic is that people have a
tendency to hardcode the shift amounts instead of using the shift amount
variables of the SDL_PixelFormat, which makes it annoying to change the
color masks of surfaces.
This commit fixes both issues by unhardcoding the bit shift amounts in
DrawPixel and ReadPixel, and by axing the `Uint32`s in favor of using
SDL_Color.
According to the SDL_PixelFormat documentation (
https://wiki.libsdl.org/SDL2/SDL_PixelFormat ), the logic to read and
draw to pixels from colors below 32-bit was just wrong. Specifically,
for 8-bit, there's a color palette used instead of some intrinsic color
information stored in the pixel itself. But we shouldn't need that logic
anyways because we don't use colors below 32-bit. So I axed that too.
This makes it so temporary variables have their scopes reduced (if
possible). I also didn't hesitate to fix style issues, such as their
names ("temp" is such a bad name), making them const if possible, and
any code it touched too.
It previously duplicated the for-loop twice, once for tiles.png and
tiles2.png, which just made me sad. Now it doesn't do that.
Also it previously had an alternate tileset == 10 condition for
tiles.png, which didn't seem to do anything because there's no such
thing as tileset 10, and anyways it's useless in-game because when
playing in the actual game it won't draw tiles.png, so I removed it. I
don't know why it was there in the first place.
Since I removed the temp variable from the outer scope, the other usage
of it has to be updated.
For some reason they all have their executable bits set. Presumably this
is because they were made on an NTFS system where every file is
executable (which doesn't sound secure at all but that's another story).
This allows translators to test all text boxes in the scripts. It
doesn't run the scripts themselves - it only shows the basic appearance
of each text box individually, so context may be lost but it's good to
have a way to see any text boxes that might otherwise not be easily
seen because they require specific circumstances to appear.
Did another complete proofread of all the non-roomnames (hadn't looked
through *everything* in a while), and it's just three little things
that I felt would be important enough to tweak.
The strings "Vitellary"/"Vermilion"/"Verdigris"/"Victoria" now have two
cases to support changing them for the intermission replay menu options
(like "with Vitellary").
Also, the string "< and > keys change tool" is now "{button1} and
{button2} keys change tool", so it can be changed dynamically without
having to retranslate the string.
I wanted to not complicate the system with different string cases (like
cgettext) if possible, and I have been able to keep the main strings a
simple English=Translation mapping thus far, but apparently strings
like "Rescued!" (which are one string in English), have to be
translated for the correct gender in some languages. So this was a good
time to add support for string cases anyway.
It's a number that can be given to a string to specify the specific
case it's used, to disambiguate identical English keys. In the case of
"Rescued!" and "Missing...", male versions of the string are case 1,
female versions are case 2, and Viridian being missing is case 3. Of
course, if a language doesn't need to use different variants, it can
simply fill in the same string for the different cases.
If any other string needs to switch to different cases: distinguish
them in the English strings.xml with the case="N" attribute (N=1 and
higher), sync language files from the translator menu (existing
translations for the uncased string will simply be copied to all cases)
and change loc::gettext("...") to loc::gettext_case("...", 1),
loc::gettext_case("...", 2), etc.
With a single README.txt for both translators and maintainers, both
have to read through info that's not relevant for them, because
translators don't need to worry about the specifics of adding new
English strings and recompiling the game, and programmers don't need to
worry about the specifics of how to translate things. Now it's split
into README-translators.txt and README-programmers.txt.
I would, of course, recommend translators to translate the roomnames
while playing the full game (optionally in invincibility) so they can
immediately get all the context and maybe the most inspiration. And if
you want to go back into a specific level, then there's always the time
trials and intermission replays which will give you full coverage of
all the room names.
However, the time trials weren't really made for room name translation.
They have some annoying features like the instant restart when you
press ENTER at the wrong time, they remove context clues like
teleporters and companions, but the worst problem is that the last room
in a level is often completely untranslatable inside the time trials
because you immediately get sent to the results screen...
So, I added a new menu in the translator options, "explore game", which
gives you access to all the time trials and the two intermissions, from
the same menu. All these time trials (which they're still based off of,
under the hood) are stripped of the annoying features that come with
time trials. These are the changes I made to time trial behavior in
translator exploring mode:
- No 3-2-1-Go! countdown
- No on-screen time/death/shiny/par
- ENTER doesn't restart, and the map menu works. The entire map is also
revealed.
- Prize for the Reckless is in its normal form
- The teleporters in Entanglement Generator, Wheeler's Wormhole and
Level Complete are restored as context for room names (actually, we
should probably restore them in time trials anyway? Their "press to
teleport" prompt is already blocked out in time trials and they do
nothing other than being a checkpoint. I guess the reason they were
removed was to stop people from opening the teleporter menu when that
was not specifically blocked out in time trials yet.)
- The companions are there at the end of levels, and behave like in no
death mode (become happy and follow you to the teleporter). Also for
context.
- At the end of each level, you're not suddenly sent to the menu, but
you can use the teleporter at your leisure just like in the
intermission replays. In the Final Level, you do get sent to the menu
automatically, but after a longer delay.
I made another mark on VVVVVV: don't be startled, I added gamestates.
I wanted all teleporters at the end of levels to behave like the ones
at the end of the intermission replays, and all handling for
teleporting with specific companions is already done in gamestates, so
rather than adding conditional blocks across 5 or so different
gamestates, it made more sense to make a single gamestate for
"teleporting in translator exploring mode" (3090). I also added an
alternative to having to use gamestate 3500 or 82 for the end of the
final level: 3091-3092.
One other thing I want to add to the "explore game" menu: a per-level
count of how many room names are left to translate. That shouldn't be
too difficult, and I'm planning that for the next commit.
This is a single block of code so it was easy to split from the
foundation commit.
This commit is part of rewritten history of the localization branch.
The original (unsquashed) commit history can be found here:
https://github.com/Dav999-v/VVVVVV/tree/localization-orig
This involves loc::gettext_roomname and loc::gettext_roomname_special.
This commit is part of rewritten history of the localization branch.
The original (unsquashed) commit history can be found here:
https://github.com/Dav999-v/VVVVVV/tree/localization-orig
This mainly adds loc::gettext calls and replaces SDL_snprintf by
VFormat for the percentage.
This commit is part of rewritten history of the localization branch.
The original (unsquashed) commit history can be found here:
https://github.com/Dav999-v/VVVVVV/tree/localization-orig
This replaces SDL_snprintf by VFormat for the time strings, and makes
number_words get translated numbers.
This commit is part of rewritten history of the localization branch.
The original (unsquashed) commit history can be found here:
https://github.com/Dav999-v/VVVVVV/tree/localization-orig
This mainly adds loc::gettext calls for all the menu titles and
explanations. It also redesigns the time trial screen.
This commit is part of rewritten history of the localization branch.
The original (unsquashed) commit history can be found here:
https://github.com/Dav999-v/VVVVVV/tree/localization-orig
This mainly adds loc::gettext calls for menu option labels.
This commit is part of rewritten history of the localization branch.
The original (unsquashed) commit history can be found here:
https://github.com/Dav999-v/VVVVVV/tree/localization-orig
setLevelDirError was changed from snprintf-style to VFormat, but it's
only used in that file so...
This commit is part of rewritten history of the localization branch.
The original (unsquashed) commit history can be found here:
https://github.com/Dav999-v/VVVVVV/tree/localization-orig
This adds loc::gettext for all "Press {button} to explode" and friends,
and also changes the interact_prompt function in Render.cpp to expect
{button} instead of %s.
This commit is part of rewritten history of the localization branch.
The original (unsquashed) commit history can be found here:
https://github.com/Dav999-v/VVVVVV/tree/localization-orig
The affected functions are:
- editormenuactionpress
- editorinput
- editorclass::switch_tileset
- editorclass::switch_tilecol
- editorclass::switch_enemy
- editorclass::switch_warpdir
This mainly adds loc::gettext calls.
This commit is part of rewritten history of the localization branch.
The original (unsquashed) commit history can be found here:
https://github.com/Dav999-v/VVVVVV/tree/localization-orig
This commit adds most of the code changes necessary for making the game
translatable, but does not yet "unhardcode" nearly all of the strings
(except in a few cases where it was hard to separate added
loc::gettexts from foundational code changes, or all the localization-
related menus which were also added by this commit.)
This commit is part of rewritten history of the localization branch.
The original (unsquashed) commit history can be found here:
https://github.com/Dav999-v/VVVVVV/tree/localization-orig
This is needed for the limits check in the translator menu.
This commit is part of rewritten history of the localization branch.
The original (unsquashed) commit history can be found here:
https://github.com/Dav999-v/VVVVVV/tree/localization-orig
This just adds booleans roomname_special to the level classes in
preparation for the localization system to use them.
This commit is part of rewritten history of the localization branch.
The original (unsquashed) commit history can be found here:
https://github.com/Dav999-v/VVVVVV/tree/localization-orig
A relevant paragraph copied from the original commit history:
The idea is that we store all strings somewhere managed, and then the
hashmap only needs pointers to those strings. For storing strings, I
created a `Textbook` structure, which consists of one or more 50 KB
"pages" (allocated as needed) on which you can simply write strings in
both languages back-to-back with `textbook_store(textbook, text)` and
get pointers to each of them. (I was originally going to just use one
big buffer and realloc to double the size when filled up, but then the
hashmap would be full of dangling pointers...) When needed, like when
switching to a different language, an entire textbook can be freed at
once.
This commit is part of rewritten history of the localization branch.
The original (unsquashed) commit history can be found here:
https://github.com/Dav999-v/VVVVVV/tree/localization-orig
This has a lot of characters that different languages need.
This commit is part of rewritten history of the localization branch.
The original (unsquashed) commit history can be found here:
https://github.com/Dav999-v/VVVVVV/tree/localization-orig
I think this is because if you both check that __has_builtin is defined
and use it in the same 'if' preprocessor statement, it can error because
there's no equivalent to short-circuiting in preprocessor statements.
_SDL_HAS_BUILTIN should be safer.
This creates the game over screen for dying in No Death Mode. It's three
lines long and it's only called once. There's no reason it has to be a
separate function. From the name it sounds like it was meant to be a
generic function but it's anything but that. So just inline it in to
where it's called.
This fixes a bug where players could flip in mid-air at the start of
custom levels whose start points were in mid-air, because
onground/onroof were not being reset. This could also be done when
loading in to a save with a checkpoint in mid-air. All that's required
is to exit the game while standing on a surface (or otherwise having a
nonzero onground/onroof).
This reminded me that there were other variables on the player entity
persisting through that made loading in to a level not have a totally
clean slate, such as walkingframe (which could affect sequencing
individual TASes together into one TAS), so it's better to just destroy
the player entity and recreate it.
Except some attributes still have to be persisted for 2.2 and 2.0
glitchrunner mode. But it's better that this is done via a whitelist
rather than a blacklist.
The player duplicate removal code in hardreset is mostly redundant now
(except for some very unlikely corner cases), but there's nothing wrong
with having redundant code as long as it's not harmful. I had a
paragraph here justifying why it could be removed but decided it was
simpler to just keep it.
This print is useful to know if an achievement (one that's not already
unlocked) would actually be unlocked in an end user environment, while
running the game in a dev environment.
Also fixed up the style of the function because it was definitely
inconsistent with the surrounding code.
This overhauls scriptclass::gamemode massively.
The first change is that it now uses an enum, and enforces using that
enum via using its type instead of an int. This is because whenever
you're reading any calls to startgamemode, you have no idea what magic
number actually corresponds to what unless you read startgamemode
itself. And when you do read it, not every case is commented adequately,
so you'd have to do more work to figure out what each case is. With the
enum, it's obvious and self-evident, and that also removes the need for
all the comments in the function too. Some math is still done on mode
variables (to simplify time trial code), but it's okay, we can just cast
between int and the enum as needed.
The second is that common code is now de-duplicated. There was a lot of
code that every case does, such as calling hardreset, setting Flip Mode,
resetting the player, calling gotoroom and so on.
Now some code may be duplicated between cases, so I've tried to group up
similar cases where possible (most notable example is grouping up the
main game and No Death Mode cases together). But some code still might
be duplicated in the end. Which is okay - I could've tried to
de-duplicate it further but that just results in logic relevant to a
specific case that's located far from the actual case itself. It's much
better to leave things like setting fademode or loading scripts in the
case itself.
This also fixes a bug since 2.3 where playing No Death Mode (and never
opening and closing the options menu) and beating it would also give you
the Flip Mode trophy, since turning on the flag to invalidate Flip Mode
in startgamemode only happened for the main game cases and in previous
versions the game relied upon this flag being set when using a
teleporter for some reason (which I removed in 2.3). Now instead of
specifying it per case, I just do a !map.custommode check instead so it
covers every single case at once.
While refactoring scriptclass::startgamemode, I noticed that these
variables weren't being reset. Fortunately, this doesn't seem to have
affected anything because they get overwritten one way or another in
startgamemode. But it's good just to be defensive and reset them anyway.
They are not reset in 2.2 or 2.0 glitchrunner mode because dying during
exiting to the menu is needed for credits warp, and that means the
checkpoint position needs to be maintained through.
This is to indicate when a code path is absolutely, for certain, 100%
unreachable. Useful as the default case inside a case-switch that is for
sure 100% exhaustive because it's inside the case of another case-switch
(and the default case is there to suppress compiler warnings about the
case-switch not being exhaustive), which is a situation coming up in my
scriptclass::startgamemode refactor.
It does this by deliberately invoking undefined behavior, either using a
compiler builtin that does the same thing or being a noreturn function
that returns. (And undefined behavior is not undefined behavior if it is
not executed in a code path, otherwise all NULL checks would be useless
because it'd dereference something that could be NULL in another code
path.)
I have no idea why they were floats in the first place. They are
coordinates, and coordinates are integer positions in this game. They
get converted to float only to be explicitly `static_cast`ed back to
ints in `testwallsy`.
This variable passes along the rule of the entity, which is an int. No
idea why it was converted to a float. Thankfully this is only used for
an unused block type, so it doesn't really matter.
The android version just got a much needed update to fix some resolution issues on devices with cutouts.
It turns out the mobile source was actually pretty out of date, like 3 versions out of date! This commit brings it up to date.
All the changes have just been about keeping the game running on modern devices, though. The biggest change was adding the Starling library to the project, which made the game GPU powered and sped the whole thing up.
This makes it so room names are no longer pointers to someone else's
memory, and instead to set them you use `mapclass::setroomname`. If the
string is short enough to fit in a static, no-alloc buffer, then it gets
copied there. Otherwise, a new heap allocation is made that duplicates
the string, and the new pointer is used instead.
This makes it possible for room names to contain arbitrary data whose
origin is temporary (e.g. from a script command that could be added in
the future).
This replaces all calls to SDL_free with a new macro, VVV_free, that
nulls the pointer afterwards. This mitigates any use-after-frees and
also completely eliminates double-frees. The same is done for any
function to free specific objects such as SDL_FreeSurface, with the
VVV_freefunc macro.
No exceptions for any of these calls, even if the pointer is discarded
or zeroed afterwards anyway. Better safe than sorry.
This is a macro rather than a function that takes in a
pointer-to-pointer because such a function would have type issues that
require casting and that's just not safe.
Even though SDL_free and other SDL functions already check for NULL, the
macro has a NULL check for other functions that don't. For example,
FAudioVoice_DestroyVoice does not check for NULL.
FILESYSTEM_freeMemory has been axed in favor of VVV_free because it
functionally does the same thing except for `unsigned char*` only.
Trinket and teleporter legends would be drawn even if they were out of
bounds. Trinket legends in particular were easy to do because you can
just place a trinket in a custom level and resize the map to not include
the room of the trinket.
Now, there are checks added so they won't be added if they are out of
bounds. This is in line with the fact that, since 2.3, if a trinket
exists outside of the map in custom levels, it won't count towards the
number of trinkets.
It's becoming pretty clear that the size of the map is important enough
to be queried a lot, but each time it's something like `map.custommode ?
map.customwidth : 20` and `map.custommode ? map.customheight : 20` which
is not ideal because of copy-pasting.
Furthermore, even `map.customwidth` and `map.customheight` are just
duplicates of `cl.mapwidth` and `cl.mapheight`, which are only set in
`customlevelclass::generatecustomminimap`. This is a bit annoying if you
want to, say, add checks that depend on the width and height of the
custom map in `mapclass::initcustommapdata`, but `map.customwidth` and
`map.customheight` are out of date because `generatecustomminimap`
hasn't been called yet. And doing the ternary there requires a `#ifndef
NO_CUSTOM_LEVELS` to reference `cl.mapwidth` and `cl.mapheight` which is
just awful.
So I'm axing `map.customwidth` and `map.customheight`, and I'm axing all
the ternaries that are duplicating the source of truth in
`MapRenderData`. Instead, there will just be one function to call for
the width and height, `mapclass::getwidth` and `mapclass::getheight`,
and everyone can simply call those without needing to do ternaries or
duplication.
The existing code was allergic to putting spaces between tokens, and had
some minor code duplication that I took the time to clean up as well.
The logic should be easier to follow now that the for-loops are no
longer duplicated for each of the map zoom levels.
I tested this by temporarily disabling map fog entirely and going
through a couple different custom levels to compare their minimap with
the existing code. These were A New Dimension and 333333 for 1x, Golden
Spiral and VVVV 4k for 2x, and VVVVVV is NP-Hard for 4x. There was no
difference in the output, not even a single pixel.
This adds color support to the output of the console on Windows. Now if
you're using Windows 10 build 1511 or later (I think it's build 1511
anyway; they added more VT sequence support in later versions), you will
see colors by default. This isn't due to Windows helping in any way;
this commit has to specifically enable it with SetConsoleMode() because
by default, Windows won't enable color support unless we enable it. (Or
if it's enabled in the registry, but having to go through the registry
to enable basic shit like that is completely fucking stupid.)
I tested this in my Windows 10 virtual machine and it's completely
working.
Previously, we were using `color_enabled` to mean both that the color
was supported and that it was enabled by the user (which it is enabled
by default). But this logic doesn't work well if the color check
function is called again and ends up enabling color after the user
disabled it. To fix this, just separate the two so the user controls one
`color_supported` variable and the `color_enabled` variable is separate.
Check both of them in order to print color, of course.
This adds the `-console` command-line option (for Win32 only) so the
game can spawn an attached console window which will contain all console
output.
This is to make it easier for people to debug on Windows systems.
Otherwise, the only way to get console output would be to either compile
the application as a console app (i.e. switch the subsystem to console)
- which is undesirable for regular users as this makes it so a console
is always spawned even when unwanted - or launch the game with shell
arguments that make it so output is redirected to a file.
As a result, color checking support is factored out of vlog_init() into
its own function, even though we don't support colors on Windows.
Using SDL_GetTicks() to seed the Gravitron RNG caused many
reproducibility issues while syncing https://tasvideos.org/7575S . To
fix this, add a frame counter, which is a number that is incremented
every frame and never resets, and use it instead.
If someone needs to switch back to SDL_GetTicks() for old TASes, then
provide the -seed-use-sdl-getticks command-line option for them.
In its previous location, it would only print the value of `s` after it
had been mutated by `splitmix32` four times, and it doesn't get used
after that, so the print isn't very useful.
Mixing code and declarations here is fine because starting from a few
months ago, we compile with C99 and if we ever need to compile with C90
then it's trivial to add braces surrounding the declarations.
If a music track has a loop comment with a negative value, ignore all
comments of the track. This is just to prevent any weirdness from
happening as it's safer to just let the track loop improperly. Also log
to the console to let users know.
This is the same thing that SDL_mixer does now:
libsdl-org/SDL_mixer@e819489459
This commit happened as a result of discussion on the VVVVVV Discord
server about SDL_mixer 2.0.4 behavior with weird loop comment values
(e.g. octal input with leading zeroes). This is simply updating the code
to be in line with what newer versions of SDL_mixer do.
Just in case it happens. Comments aren't really important to the game
(at worst a track will just loop in the wrong place) so it's fine to
carry on here and ignore all comments if this happens.
This does the following:
- Const-qualify variables if they are not modified
- Place each statement onto their own separate lines
- Place the asterisk with the type instead of the variable name
- Combine declarations and initializations where possible
VVVVVV uses submodules now, so you need to know how to initialize them.
I'm explicitly not including `git clone --recurse-submodules`. Usage of
submodules in git projects is kinda rare in my experience, so people
are used to doing simple clones, and that instruction would just result
in people being annoyed thinking they have to delete the repo they
already cloned, and clone it again except slightly differently.
It also doesn't help you if you need submodules that aren't in the
master branch (for example, if you clone my fork recursively and then
checkout the localization branch, you won't have C-HashMap and you'll
need the update command anyway). And you also need it whenever VVVVVV
updates its submodules. So teaching people just the update command is
better.
This updates FAudio, LodePNG, and PhysFS. All other submodules do not
have updates.
The most notable is FAudio, which updates to SDL 2.24.0 with many fixes
that, as Ethan put it, you definitely want.
They weren't ever being used, and nobody really ever uses the return
value from the printf family of functions anyway. They return how many
bytes were actually printed, but if it's less than you expected then
there's not much you can really do about them. Also the vlog_* functions
were computing them inaccurately because I only set the return value to
the return value of vprintf when there's other print functions being
called, but regardless there's no reason to have a return value here
anyway.
The hilariously-named WIN32_LEAN_AND_MEAN slims down the number of
header files included in the already-massive `windows.h`. I know people
say Moore's law and precompiled headers and all that (well, we don't use
precompiled headers), but they kinda forgot about virtual machines, and
there's no reason not to define this and slim down the number of headers
anyway.
This started when I saw the warning that GetVersionExW was deprecated,
then looked it up and found StackOverflow answers saying that you should
basically detect the feature directly instead of checking the version,
which makes sense to me. Then I found that I could probably detect color
support by using GetConsoleMode and GetStdHandle. But then I asked
myself what the point was unless you could get color output directly in
the terminal, which it seems like you really can't if your app is a GUI
app. (I have no idea why Windows makes this pointless distinction
between console and GUI apps...)
I tested Command Prompt, PowerShell, Windows Terminal (which is just
PowerShell again), and even Git Bash (MINGW64), but none of them will
ever give the console output of a GUI app such as VVVVVV. The closest I
got is that Git Bash doesn't seem to detach the process, but it will
simply produce no output.
At this point I feel like it's not worth it keeping this code around if
it didn't even work in the first place, so I'm removing it. People can
always enable color by using the -forcecolor command-line argument
anyway.
I'm fine with putting the release version in a header file, thus
necessitating the need to recompile every file that includes it if it's
changed, simply because it's not supposed to be changed that often.
The SDL_arraysize is necessary because sometimes we'll have subreleases
(e.g. 2.4.1, 2.4.2, 2.4.3), and who knows, maybe we'll get to 2.10
someday.
This reworks how the commit hash and date are compiled so that if
they're changed (and they're changed often), only one source file needs
to be recompiled in order to update it everywhere in the game, no matter
how many source files use the hash or date.
The commit hash and date are now declared in InterimVersion.h (and they
need `extern "C"` guards because otherwise it results in a link fail on
MSVC because MSVC is stupid).
To do this, what now happens is that upon every rebuild,
InterimVersion.in.c is processed to create InterimVersion.out.c, then
InterimVersion.out.c is compiled into its own static library that is
then linked with VVVVVV.
(Why didn't I just simply add it to the list of VVVVVV source files?
Well, doing it _now_ does nothing because at that point the horse is
already out of the barn, and the VVVVVV executable has already been
declared, so I can't modify its sources. And I can't do it before
either, because we depend on the VVVVVV executable existing to do the
interim version logic. I could probably work around this by cleverly
moving around lines, but that'd separate related logic from each other.)
And yes, the naming convention has changed. Not only did I rename
Version to InterimVersion (to clearly differentiate it from
ReleaseVersion, which I'll be adding later), I also named the files
InterimVersion.in.c and InterimVersion.out.c instead of
InterimVersion.c.in and InterimVersion.c.out. I needed to put the file
extension on the end because otherwise CMake wouldn't recognize what
kind of language it is, and I mean like yeah duh of course it doesn't,
my text editor doesn't recognize it either.
I thought all of these were removed earlier but apparently not. Anyways,
add_definitions is bad because it pollutes the definitions of every
single target, we should be using target_compile_definitions instead.
I missed this because to check for all instances of 2.0.22, I did `rg -F
'2.0.22'`. But ripgrep doesn't search in hidden directories by default,
so the actual command to run is `rg -F. '2.0.22'`.
This updates all references to SDL 2.0.22 to SDL 2.24.0, including the
Docker container that I maintain for Linux CI.
Be warned, this release of SDL updates the versioning scheme to be less
dumb. The previous version is 2.0.22, this release is 2.24.0 (so the
last number can be properly used for patch-level version releases).
This option is enabled by default and will replace absolute paths of all
source directory file paths with relative paths in the compiled binary,
if the compiler supports it. Of course, this isn't needed if you compile
with all paths removed anyways (e.g. in Release mode).
The purpose is to help make builds reproducible and to remove any
potentially sensitive information about the user or the user's system
from the compiled binary.
Both Clang and GCC support -fdebug-prefix-map, -fmacro-prefix-map, and
-ffile-prefix-map. In particular, -ffile-prefix-map is just a flag that
does both -fdebug-prefix-map and -fmacro-prefix-map.
According to https://reproducible-builds.org/docs/build-path/ ,
-fdebug-prefix-map is available in all GCC versions but only available
starting from Clang 3.8, and -fmacro-prefix-map and -ffile-prefix-map
are available since GCC 8 and Clang 10. So we check the compiler version
and use the available flags depending on if the compiler supports it or
not.
This does make debugging a bit more annoying, but there are a couple
ways to rectify this. Either disable it with
-DREMOVE_ABSOLUTE_PATHS=OFF, or add a `.gdbinit` that consists of
set substitute-path . ../..
so that `.` is considered to be `../..`. Of course, if you need to,
replace `../..` with the actual source directory path (in my case it's
`../../..` because I place my build folders in another subdirectory to
have multiple build folders in one directory).
This doesn't need to be a global `.gdbinit`, it can be in a
directory-specific `.gdbinit` (similar to how `.gitignore`s can also be
directory-specific). But then you need to add `add-auto-load-safe-path`
to your `.gdbinit` to load any directory-specific `.gdbinit`s.
The above is for GDB; I don't know what (if anything) needs to be done
for LLDB; I don't use LLDB.
Fixes#889.
Whereas all `SDL_assert`s will go away when compiling with optimization
flags and all plain `assert` calls (used in PhysFS) will go away when
compiling in Release mode, FAudio has a bunch of debug stuff that needs
to be explicitly disabled with its own `FAUDIO`-prefixed flag.
To do this in Release mode, we need to use generator expressions for
dumb CMake reasons. Basically, if checking the CMAKE_BUILD_TYPE variable
will not work for certain generators (Ninja, Visual Studio) because they
only specify the build type at build time, not generation/configuration
time.
This is so flags that apply globally (i.e. to the game and all static
libraries it's compiled with), such as /MT on MSVC, can be put in a
list, and along with putting all static libraries in a list, we remove
the need for each flag to be repeated for each static library and we can
just use a foreach loop instead.
(Global compile flags of course don't apply to us meddling with
CMAKE_C_FLAGS and CMAKE_CXX_FLAGS directly, because we need to do that
in order to make sure the C and C++ standards are set properly.)
This fixes a regression where the game ignored the amount of frames you
held down a direction if you released the direction during death.
Previously, the game only checked the amount of frames you held down a
direction if you were able to control the player. If you weren't able to
control the player (e.g. during the death animation), then the number of
frames it counted didn't change. This also meant that if you were
holding a direction before you died, but released it during death, the
game wouldn't zero out the number of frames you held it.
This behavior was useful because it meant you could keep the
deceleration momentum that you normally get by holding a direction for 5
frames just by holding a direction for less than 5 frames after dying,
if you had the rest of the hold frames before you died. This behavior is
what's used in https://tasvideos.org/7575S at around frame 7200.
Unfortunately, #609 made it so that the direction hold processing
happened even if the player didn't have control, meaning that it would
zero the hold frames during the death animation in the TAS, thus
desyncing it when it performed the maneuver it relied on the extra
momentum for after Viridian respawns.
The solution here is to just add the check back in again.
Fixes#887.
While fixing #885, I noticed that I had a bunch of
`special/stdin.vvvvvv` entries saved in my `levelstats.vvv`. At once I
knew that the dumb `special/stdin` hack that actually checks if the
filename passed is `special/stdin` was to blame.
STDIN playtesting was first merged, I knew in the back of my mind that
it was a bit of a dumb hack, but I didn't know it would cause
consequences like showing up in `levelstats.vvv`. For now, I'll just
have to patch it, but hopefully in the future I'll remove the dumb hack
entirely. Commenting both instances of the dumb hack with instructions
to grep for it should help maintainers out.
This is useful to investigate any TAS desync/reproducibility issues
relating to RNG, because even though I specifically separated the
Gravitron RNG away from other RNG and made it not dependent on the
system libc rand() function, there's still apparently some differences
in RNG execution between systems, resulting in TASVideos submission 7575
( https://tasvideos.org/7575S ) not syncing for everyone except the
author.
It seems that SDL_GetTicks(), which is used to seed the xoshiro RNG, is
not reliably consistent between systems, so in the future I will
probably replace it with a counter that is incremented each frame
starting from game startup, which is probably better.
This fixes the following warnings:
desktop_version/src/Music.cpp: At global scope:
desktop_version/src/Music.cpp:240:23: warning: non-static data member initializers only available with ‘-std=c++11’ or ‘-std=gnu++11’ [-Wc++11-extensions]
240 | Uint8 *wav_buffer = NULL;
| ^
desktop_version/src/Music.cpp:414:32: warning: non-static data member initializers only available with ‘-std=c++11’ or ‘-std=gnu++11’ [-Wc++11-extensions]
414 | Uint8* decoded_buf_playing = NULL;
| ^
desktop_version/src/Music.cpp:415:32: warning: non-static data member initializers only available with ‘-std=c++11’ or ‘-std=gnu++11’ [-Wc++11-extensions]
415 | Uint8* decoded_buf_reserve = NULL;
| ^
desktop_version/src/Music.cpp:416:21: warning: non-static data member initializers only available with ‘-std=c++11’ or ‘-std=gnu++11’ [-Wc++11-extensions]
416 | Uint8* read_buf = NULL;
| ^
These warnings are because the non-static data members (i.e. data
members that will be different for each instance of a class) are being
initialized at the same time as they're being declared, which means
that's what their value will be when the class instance is initialized.
However, this is only a C++11 feature, and we don't use C++11. Luckily,
we don't need to do this, and this is in fact redundant because we
already zero out the class instance in its constructor using
SDL_zerop(). Therefore, we should remove these initializers to fix
compliance with C++03.
Instead, for any number that isn't in the list of number words, just
return the regular Arabic numerical representation (i.e. just convert it
to string). It's better than having "Lots" or "???", neither of which
really tell you what the number actually is.
This flag makes it so the MSVC runtime libraries are statically linked.
This avoids needing Windows users to have these libraries installed.
Apparently /MT stands for "MultiThreaded", and there's a bit of a
history there where originally by default you could only have a
single-threaded library, and then the multi-threaded flags were added in
later.
First I tried doing target_compile_options on VVVVVV, but then got a
linker error. Then I tried doing add_compile_options because I figured
/MT had to be applied everywhere, and it seemed to work, but it still
linked to the runtime libraries. Apparently it was being overridden.
Then I tried target_compile_options again but this time did it to
everything, and that linked correctly and also removed the runtime
dependency. I would've tried using the MSVC_RUNTIME_LIBRARY property
- along with the CMP0091 policy - but those were only introduced in
CMake 3.15.
You can verify that a binary is built without dependencies by installing
LLVM and running llvm-readobj --needed-libs path/to/binary. This is the
output for a binary with runtime dependencies:
infoteddy@fedorarune ~/d llvm-readobj --needed-libs VVVVVV.exe
File: VVVVVV.exe
Format: COFF-i386
Arch: i386
AddressSize: 32bit
NeededLibraries [
ADVAPI32.dll
KERNEL32.dll
MSVCP140.dll
SDL2.dll
SHELL32.dll
USER32.dll
VCRUNTIME140.dll
api-ms-win-crt-heap-l1-1-0.dll
api-ms-win-crt-locale-l1-1-0.dll
api-ms-win-crt-math-l1-1-0.dll
api-ms-win-crt-runtime-l1-1-0.dll
api-ms-win-crt-stdio-l1-1-0.dll
api-ms-win-crt-string-l1-1-0.dll
api-ms-win-crt-time-l1-1-0.dll
api-ms-win-crt-utility-l1-1-0.dll
]
And this is the output for a binary with those dependencies having been
statically-linked in:
infoteddy@fedorarune ~/d llvm-readobj --needed-libs VVVVVV.exe
File: VVVVVV.exe
Format: COFF-i386
Arch: i386
AddressSize: 32bit
NeededLibraries [
ADVAPI32.dll
KERNEL32.dll
SDL2.dll
SHELL32.dll
USER32.dll
]
As already described in cc61194bed, as
well as Ved's commits from the last almost two weeks, starting VVVVVV
from Ved for playtesting could be made a lot faster by "preloading" the
game - letting it do all its asset loading in the background without
creating a window - and then waiting until the level is passed in via
stdin. There's only one problem left with this approach: VVVVVV
currently expects the starting position to be passed via command line
arguments, which isn't known yet at the time we'd like to start VVVVVV.
Therefore, this commit allows passing the starting position via the
level XML, instead of via arguments.
The extra XML looks like this, and is added next to the <Data> tag:
<Playtest>
<playx>214</playx>
<playy>112</playy>
<playrx>100</playrx>
<playry>100</playry>
<playgc>0</playgc>
<playmusic>4</playmusic>
</Playtest>
This is handled similarly to how the equivalent arguments are handled:
when the level metadata is loaded for CLI playtesting, we also try to
find this tag, and if it exists, it sets the same variables that the
arguments would have otherwise set.
There's always been a bit of an inconsistency in the game where enabling
invincibility would make spikes so solid that enemies and moving
platforms would treat spikes as solid and bounces off of them.
This fixes that by adding an `invincible` parameter to collision
functions, so the functions will only treat spikes as solid if that
parameter is true, and the parameter passed will only be true if it's
called for an entity that is a humanoid and invincibility mode is
enabled.
Also, for clarity, `spikecollide` is renamed to `towerspikecollide`
because it's only used for tower spikes. And as a small optimization,
`checktowerspikes` returns early if invincibility mode is enabled.
This is a minor optimization to streamline the experience of Ved
playtesting. Previously, the user would have to wait for all the assets
to load when launching playtesting (most of the time, I suspect, is
taken up by loading music from a vvvvvvmusic blob). With this
optimization, however, the game can be launched in the background and
its assets can be loaded, while it blocks on STDIN input. During this
time, the user in Ved will be choosing where to start playtesting. After
Ved provides STDIN input, then the window will be created and appears
instantaneously.
This also fixes a related issue in which providing an invalid
playtesting level name would result in a brief window flash that gets
instantly destroyed. With this, if the level is invalid then no window
is ever shown at all.
Probably should have done this earlier in 2.3, but better late than
never.
This makes it easier for third-party programs like Ved to detect what
version of the game this is.
Slightly quick-n-dirty for now, I'll de-duplicate the version number
later, and add commit hash and date if applicable.
Doesn't hurt to keep things up to date. (The other submodules, UTF-CPP
and LodePNG, haven't been updated since then.) In particular, PhysFS has
worked around a bug with Windows Explorer, so people should no longer
have issues modifying their data.zip with Explorer and then being unable
to have assets in their game (as reported in icculus/physfs#24 ).
Without this, entering in-game and opening the map with missing graphics
will result in a segfault. This is because even if the image doesn't
exist, it's still pushed into the `images` std::vector as a NULL
pointer. And it segfaults because we dereference it (to get things like
their width and height). In contrast, passing them to SDL is fine
because SDL always checks for NULL first.
There are three different places where we call PHYSFS_openRead. This
commit makes sure all of them print a statement upon failure along with
the PhysFS reason for failure, and assigns the log level of each print
as so:
- FILESYSTEM_loadFileToMemory: Debug print (previously no
print existed in the first place), because some files (such as
font.txt) may or may not be needed, but if it isn't then no need to
print and worry the user. The game will error anyway if a critical
file like a graphics file is missing.
- FILESYSTEM_loadBinaryBlob: Debug print (previously info print),
because sometimes it's not needed, such as mmmmmm.vvv. I remember one
user being worried that the game printed "Unable to open file
mmmmmm.vvv" when it's not critical unlike vvvvvvmusic.vvv (and that
file is assumed to exist if data.zip exists anyways). Though maybe we
should move to loose-leaf files to save on memory usage (and so we
don't have to use special tools to modify a binary blob)...
- FILESYSTEM_loadZip: Error print. If we're calling this function, we
really do expect the zip to be loaded, and if it fails because we
can't open the file in the first place, then it would be good to know
why.
This commit adds a new string formatting system to replace uses of
`SDL_snprintf` and string concatenation.
Making our own string formatting system has been briefly discussed
during the review of the localization branch, and on the VVVVVV
Discord. It's inspired by Python's format strings, but simpler.
This is primarily to benefit localization - strings will be easier to
understand (`Now using %s Tileset` → `Now using {area} Tileset`,
`"%s remain"` → `"{n_crewmates|wordy} remain"`), translators can change
the word order for their language's grammar (`%1$s` is a POSIX
extension), and this system is also less error-prone (making the format
string not align with the actual arguments won't result in a crash or
UB).
It also integrates our needs better - particularly the "wordy" numbers
without having to have a `help.number_words(n).c_str()` at the
callsite, translators can opt in and out of wordy numbers per string,
and this should also make it easier to solve #859.
This commit adds the formatting system itself, and changes one
`SDL_snprintf` in the code to use it as a small demo (the rest should
probably be done in the localization branch to avoid more unneeded
work).
The system is described in full detail in VFormat.h and in the pull
request description.
2.0.22 just released 40 minutes ago.
This also updates the `Dockerfile` to use the URL from the GitHub
releases page, instead of SDL's servers.
I've also pushed a new Docker container to
`ghcr.io/infoteddy/vvvvvv-build`.
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.
Previously, it was copy-pasted and slightly different, when really, they
ought to both be the exact same code.
It kind of pains me that the room name, glitch name, and hidden name
don't own their own memory, but, that's to be addressed later.
What's a bit annoying is that the `temp` variable used in
`teleporterrender` also ends up being reused later in the function. In
this case, I opted to just redeclare them when they are used anyway, to
make it clearer.
Apart from `teleporterrender` no longer calling `map.area` or caring
about `map.custommode`, it also no longer cares about
`graphics.fademode` being 0. I could never actually get this condition
to be false in practice, and I have absolutely no idea why it's there.
I'm guessing it could be some weird edge case rendering issue if the
screen is fully black? But I wouldn't know how to trigger that, and
anyway it should probably be fixed elsewhere. So I'm just going to
remove that conditional.
It's quite rare, though possible, that during finalstretch you could see
a glitchy tileset that looked like this:
https://i.imgur.com/V7cYKDW.png
This happened because final_mapcol, the variable that controls which
color of finalstretch is rendered, could end up being 7. Normally, it's
in the range of 1..6, which perfectly correlates with the Warp Zone
tilesets in tiles2.png, and the higher the number the farther back in
the tileset it goes from the gray Warp Zone tileset. However, if it's 7,
then it'll start grabbing tiles from the Ship plus some unused blank
tiles, which does not look pretty in the slightest.
This happened because it's possible, though exceedingly unlikely, that
fRandom(), a function which returns a float between 0..1, could return
exactly 1. fRandom() calls rand(), which returns a result between 0 and
RAND_MAX, and divides it by RAND_MAX. This value is implementation
dependent, but required to be at least 32767, and on most systems is
2147483647. Even taking the value of 32767, that means there's a 0.003%
chance that you could get this glitchy tileset when the game cycled the
color in finalstretch. But of course, playing the game for long periods
of time will eventually increase this chance - cycling the color 1,000
times (around 17 minutes of playing) will result in the chance being 3%.
Then as the calculations in the finalstretch color cycling logic calls
fRandom(), then multiplies by 6 and adds 1, it was possible for
fRandom() to return exactly 1, then have
6 added to it, resulting in final_mapcol being 7.
To fix this, just decrement the multiplication by fRandom() to multiply
by 5 instead of 6. Now the only possible numbers that calculation can
produce would be 1..6.
This fixes a limitation where the level filename had to be the exact
same name as the name of the zip, because the game used the name of the
level to identify the zip of which to load assets, and this also made it
impossible to use assets for more than one level in a zip.
Instead, we just look up where the level came from, so we can always
load its assets regardless of its filename.
Additionally, the zip structure checks can go away too, simplifying the
code further.
This WOULD be a huge breaking change, if it weren't for the fact that no
one uses them. Which is why I'm removing them, to simplify the code.
I asked on the VVVVVV Discord whether anyone used them or was even aware
of them and basically the answer was no. I go on Distractionware and no
one uses them. And why would they, when they'd have to distribute the
level .vvvvvv file separately? Better to just distribute everything in
one zip. And it's quite a bit obscure that you have to suffix the file
with .data.zip anyway.
During review of #869, I looked at this part of the codebase again. I
have no idea how or why, but during the course of 2.4 this whole area
just became a mess.
The issues I fixed (in no particular order):
- Copy-pasting the code that loads from the binary blobs
- Making sure SDL_RWFromConstMem is used over SDL_RWFromMem wherever
possible
- Adding checks to make sure the index from the binary blob is valid
(it's possible it could not exist)
- Adding checks to make sure we gracefully handle
SDL_RWFromConstMem/PHYSFSRWOPS_openRead returning NULL
- Moving the pointer asterisk to the type instead of the name :)
So, it turns out we weren't quite done fighting CMake yet...
To accommodate #869 (and actually also #272), the C standard was raised
from C90 to C99. This turned out to require a bit of a fight with the
CentOS CI's CMake version to get it to set the flags we wanted (and to
not overwrite them later). Eventually the fix was to move the block
that sets the standards to later in the file, which was done in
24353a54bb.
As it apparently turns out, if your CMake is at least 3.1.3 and
`CMAKE_<LANG>_STANDARD` is used instead of the workaround, the standard
setting now has an effect on the third party libraries, but not on
VVVVVV itself. The cause is (probably) the phrase "if it is set when a
target is created" in the CMake documentation - the
`CMAKE_<LANG>_STANDARD` values have to come before the VVVVVV target is
defined. In other words, the compiler's default C/C++ standard will be
used, probably something like C17 and C++17. As I can confirm with
`__cplusplus` and `__STDC_VERSION__` with my recent-enough CMake. If I
force the pre-3.1.3 workaround to be used, everything is compiled with
C99/C++98 as expected; and the `-fno-exceptions` `-fno-rtti` flags
appear everywhere regardless of version.
So my fix is to make the CMakeLists a little less complex by
simplifying away the `CMAKE_<LANG>_STANDARD` and
`CMAKE_<LANG>_EXTENSIONS`, and always using the workaround regardless
of CMake version. There's nothing wrong with the workaround, the same
thing is also done for `-fno-exceptions` `-fno-rtti`, and it's good to
have a less complicated CMakeLists that doesn't do different and
unexpected things for different versions.
The previous commit f6d7a214f8 ended up
breaking CI because the workaround ended up breaking the PhysFS build
too, which was previously relying on extensions to compile.
Since #869 is going to require C99 anyways, I might as well just up the
standard now. That way the PR won't have to fight it too.
Previously, if the user had a CMake version below 3.1.3, we told them to
set `-std` themselves.
However, we are going to go to C99 soon (because of FAudio, see #869),
and CentOS 7's CMake is too old to set `-std=` automatically, defaulting
to C90. This is bad because it fails the CI.
To work around this, we set `-std=` ourselves, but first we have to
clear any existing `-std=` flag in C_FLAGS or CXX_FLAGS. Amusingly
enough, MSVC does not have `/std:` switches for either C90 or C++98, so
we just get to do nothing.
This isn't necessary, but it does silence these annoying logs if you
pass an invalid argument or don't have data.zip:
[ERROR] Could not get window size: Invalid renderer
[WARN] Stats not loaded! Not writing unlock.vvv.
[ERROR] Could not get window size: Invalid renderer
[WARN] Settings not loaded! Not writing settings.vvv.
To do this, I've added FILESYSTEM_isInit().
This means we are no longer copy-pasting PhysFS source files directly.
Since the source files reside in a src/ subdirectory, the paths in the
CMakeLists.txt have to be adjusted.
We are no longer copy-pasting LodePNG source files directly.
As we can't rename lodepng.cpp to lodepng.c in the submodule itself, we
need to make a wrapper file, lodepng_wrapper.c, that #includes
lodepng.cpp, but gets compiled as C.
This prevents writing to unlock.vvv or settings.vvv if the game hasn't
made an attempt to load them yet. Otherwise, if the game aborted via
VVV_exit() because of, say, failure to parse a graphics file, it would
overwrite perfectly existing valid save data since it hasn't loaded it
yet.
Fixes#870.
Another cause of #870 is d0ffafe117, as a
bisect tells me. What that commit did is remove screenbuffer as a
pointer, since it's a statically-allocated object that _should_ always
exist, and it removed the `screenbuffer == NULL` guards in savestats()
and savesettings(). Unfortunately, those guards did something very
important - namely, they prevented writing to the save files when the
filesystem wasn't initialized. But that wasn't made clear, because it
seemed like the point of those guards was to prevent dereferencing NULL.
So instead, explicitly make it clear that
FILESYSTEM_saveTiXml2Document() needs to fail if the filesystem isn't
initialized. I've done this by adding an isInit bool to
FileSystemUtils.cpp.
Issue #870 showed one of the problems that this game has, namely that it
only sometimes checks SDL return values, and did not do so in this case.
Part of the cause of #870 is that Screen::GetWindowSize does not check
the return value of SDL_GetRendererOutputSize, so when that function
fails (as in the case where m_renderer is NULL and does not exist), it
does not initialize the out values, so it ends up writing uninitialized
values to the save files.
We need to make sure every function's return value is checked, not just
SDL functions, but that will have to be done later.
While reviewing #272, I noticed that the PR was passing these two
arguments through a helper function, even though they really shouldn't
ever change. To obviate the need to pass these through, I'm making them
global variables.
pathSep is just a string literal from PhysFS, while basePath is a whole
complicated calculation from SDL and needs to be freed. It will be freed
upon filesystem deinit (as is done with PhysFS and the STDIN buffer).
Additionally the logic in FILESYSTEM_init is simplified by no longer
needing to keep a retval variable or use gotos to free basePath in
there.
This lets any script name use capitals and spaces all they want, while
still being able to jump to them via iftrinkets() or similar.
The issue is that whenever tokenize() is ran, all spaces are stripped
and every argument is lowercased before being put into `words`. So, the
solution here is to create a raw_words array that doesn't perform space
stripping or lowercasing, and to refer to that whenever there's a script
command that loads a script. We keep the lowercasing and space removal
elsewhere to be more forgiving to newcomers.
This is technically a forwards compatibility break, but it's only a
minor one, and all levels that utilize it can still be easily modified
to work on older versions anyway.
As reported by Dav999, Victoria and Vermilion's trophy colors are
swapped again in 2.4. He points to
37b7615b71, the commit where I fixed the
color masks of every single surface to always be RGB or RGBA.
It sounded plausible to me, because it did have to do with colors, after
all. However, it didn't make sense to me, because I was like, I didn't
touch the trophy colors at all after I originally fixed them.
After I ruled out the RGBf() function as a confounder, I decided to see
whether intentionally reversing the color order in RGBf() to be BGR
would do anything, and to my surprise it actually swapped the colors
back around and it didn't actually look bad.
And then I realized: Swapping the trophy colors between RGB and BGR
ordering results in similar colors that still look good, but are simply
wrong, but not so wrong that they take on a color that no crewmate uses,
so it'd appear as if the crewmates were swapped, when in reality the
only thing that was swapped was actually the color order of the colors.
Trying to fix this by swapping the colors again, I actively confused
colors 33 and 35 (Vermilion and Victoria) with colors 32 and 34
(Vitellary and Viridian), so I was confused when Vermilion and Victoria
weren't swapping. Then as a debugging step, I only changed 34 to 32
without swapping 32 as well, and then finally noticed that I was
swapping Vitellary and Viridian, because there were now two Vitellarys.
And then I was reminded that Vitellary and Viridian were also wrongly
swapped since 2.0 as well.
And so then I finally realized: The original comments accompanying the
colors were correct after all. The only problem was that they were fed
into a function, RGBf(), that read the colors backwards, because the
codebase habitually changed the color order on a whim and it was really
hard to reason out which color order should be used at a given time, so
it ended up reading RGB colors as BGR, while it looked like it was
passing them through as-is.
So what happened was that in the first place, RGBf() was swapping RGB to
BGR. Then I came and swapped Vermilion and Victoria, and Vitellary and
Viridian around. Then later I fixed all the color masks, so RGBf()
stopped swapping RGB and BGR around. But then this ended up swapping the
colors of Vermilion and Victoria, and Vitellary and Viridian once again!
Therefore, swapping Vermilion and Victoria, and Vitellary and Viridian
was incorrect. Or at least, not the fix to the root cause. The root
cause would be to swap the colors in RGBf(), but this would be sort of
confusing to reason about - at least if I didn't bother to just type the
RGB values into an image editor. But that doesn't fix the real issue,
which is that the game kept swapping RGB and BGR around in every corner
of the codebase.
I further confirmed that there was no more RGB or BGR swapping by
deleting the plus-one-divide-by-three transformation in RGBf() and
seeing if the colors looked okay. Now with the colors being brighter, I
could see that passing it straight through looked fine, but
intentionally reversing it to be BGR resulted in colors that at a
distance looked okay, but were either washed out or too bright. At least
finally I could use my 8 years of playing this game for something.
So in conclusion, actually, 37b7615b71
("Fix surface color masks") was the real fix, and
d271907f8c ("Fix Secret Lab Time Trial
trophies having wrong colors") was the real regression. It's just that
the regression came first, but it wasn't really a regression until I did
the other fix, so the fix isn't the regression, the regression is...
this is hurting my brain. Or the real regression was the friends we made
along the way, or something like that.
This is the most trivial bug ever caused by the technical debt of those
god-awful reversed color masks.
---
This reverts commit d271907f8c.
Fixes#862.
This replaces vcpkg with simply downloading the pre-compiled
dependencies from official upstream releases. The rationale is that
vcpkg is sometimes really slow to update to the latest SDL version when
it releases, and also that it requires the runner to compile SDL every
single time it's instantiated, which is slow and wasteful.
Instead, download the pre-compiled binaries of SDL from its release page
on GitHub. This way, we don't have to compile it ourselves, and we
aren't waiting on vcpkg whenever SDL releases a new version. And for
good measure, cache it so we aren't downloading it _every_ time, which
is even more efficient.
The same can't be done for SDL_mixer, though, because it doesn't have a
GitHub release page with pre-compiled binaries. Instead, we'll download
them from libsdl.org, which is an infrastructure that takes more strain
than if we used GitHub instead. But, it shouldn't matter anyways,
because we cahe this too. And we are going to ditch SDL_mixer for FAudio
in 2.4 anyways, so it's a moot point either way.
In hindsight, the FAudio pointer will likely be in SoundTrack since we will
want to keep the mastering voice closer to the sounds and their source voice
arrays, while the MusicTrack will likely just be one source voice that gets
PCM from different streams.
This looks redundant but will actually help in the transition to FAudio; we
mostly want to keep the game logic the same while reimplementing the current
mixer, weirdness and all. Once that's done and confirmed to be stable and
consistent we can start cutting out the workarounds and quirks.
This meant making the track vectors static, but that's kind of what we do with musicclass anyway?
In any case, this will make the transition to FAudio MUCH less invasive.
This is quite simple. Just use a function pointer that switches out
which function we're going to use.
...Or not. C++ syntax makes this a bit awful since the function is a
member of a class. Did I mention how much I don't like C++?
Issue #849 suggested making integer be the default on Big Picture and
Steam Deck, but after thinking about it more, I think it's better and
more simple to just default to integer mode in general.
Reason being that people in Big Picture shouldn't expect the picture to
look different if they're out of Big Picture but still in fullscreen, or
have the picture look different in fullscreen depending on if they
launched the game for the first time in Big Picture or not. And besides,
the less lines of code, the better. So I'm just making integer mode the
default.
This enum is to just make each mode be readable, instead of mysterious
0/1/2 values. It's not a strictly-typed enum because we still have to
serialize it as ints in the XML, but it's better than just leaving them
as ints.
This also adds a NUM_SCALING_MODES enum, so we don't have to hardcode
that 3 when cycling scaling modes anymore.
This is mainly to make sure the game is definitely set to fullscreen in
Big Picture and on the Steam Deck, and to also remove windowed options
that wouldn't make sense if you're not on a desktop (toggling
fullscreen, resize to nearest). Those options would also be removed on
console and mobile too.
There's a bit of an annoying bug where if you launch the game in forced
fullscreen mode, but then exit and relaunch in normal mode, your game
will have fullscreen window sizes but it won't be fullscreen. This is
because forced fullscreen mode tries to preserve your non-forced
fullscreen setting, but due to the way window sizes are stored and
queried, it can't preserve the non-forced window size. This is a bit
difficult to work around, so I'm just putting in a FIXME here because we
can fix it later and I'd rather have a slightly buggy forced fullscreen
mode than not have one at all.
Closes#849.
Here's my notes on all the existing functions and what kind of time
formats they output:
- Game::giventimestring(int hrs, int min, int sec)
H:MM:SS
MM:SS
- Game::timestring()
// uses game.hours/minutes/seconds
H:MM:SS
MM:SS
- Game::partimestring()
// uses game.timetrialpar (seconds)
MM:SS
- Game::resulttimestring()
// uses game.timetrialresulttime (sec) + timetrialresultframes (1/30s)
MM:SS.CC
- Game::timetstring(int t)
// t = seconds
MM:SS
- Game::timestringcenti(char* buffer, const size_t buffer_size)
// uses game.hours/minutes/seconds/frames
H:MM:SS.CC
MM:SS.CC
- UtilityClass::timestring(int t)
// t = frames, 30 frames = 1 second
S:CC
M:SS:CC
This is kind of a mess, and there's a lot of functions that do the same
thing except using different variables. For localization, I also want
translators to be able to localize all these time formats - many
languages use the decimal comma instead of the decimal point (12:34,56)
maybe some languages really prefer something like 1時02分11秒44瞬...
Which I don't know to be correct, but it's good to be prepared for it
and not restrict translators arbitrarily to only changing ":" and "."
when we can start making the system better in the first place.
I added a new function, UtilityClass::format_time. This is the place
where all time formats come together, given the number of seconds and
optionally frames. I have simplified the above-mentioned functions
somewhat, but I haven't given them a complete refactor or renaming -
I mainly made sure that they all use the same backend so I can make the
formats consistent and properly localizable.
(And before we start shoving more temporary char buffers everywhere
just to get rid of the std::string's, maybe we need to think of a
globally used working buffer of size SCREEN_WIDTH_CHARS+1, as a
register of sorts, for when any line of text needs to be made or
processed, then printed, and then goes unused. Maybe help.textrow,
or something like that.)
As for this commit, the available time formats are now more consistent
and changed a little in some places. Leading zeroes for the first unit
are now no longer included, time trial results and the Super Gravitron
can now display hours when they went to 60 minutes before, and we now
always use .CC instead of :CC. These are the formats:
- H:MM:SS
- H:MM:SS.CC
- M:SS
- M:SS.CC
- S.CC (only used when always_minutes=false, for the Gravitrons)
Here's what changes to the current functions:
- Game::partimestring() is removed - it was used in two places, and
could be replaced by game.timetstring(game.timetrialpar)
- Game::giventimestring(h,m,s) and Game::timestring() are now wrappers
for the other functions
- The four remaining functions (Game::resulttimestring(),
Game::timetstring(t), Game::timestringcenti(buffer, buffer_size)
and UtilityClass::timestring(t)) are now wrappers for the "central
function", UtilityClass::format_time.
- UtilityClass::twodigits(int t) is now unused so it's also removed.
- I also added int UtilityClass::hms_to_seconds(int h, int m, int s)
This de-duplicates the code, simplifying the codebase and reducing the
number of code paths that needs to be maintained. It also adds
robustness checks to LoadIcon that weren't there before (checking that
loading the file succeeded and that decoding the file also succeeded).
Now, you might think that loading the image with alpha will change
things in some way. But actually, I tested it, and I'm pretty sure it
doesn't. Since my window manager, i3, doesn't display icons, I've had to
resort to this hacky multi-liner
( https://unix.stackexchange.com/a/48866 ) to dump the icon to a PAM
file. I don't know what a PAM file is and all my various attempts to
convert it into something readable failed. But what I did instead was
just grab the icon of the game before this commit (on 2.3, just to be
extra sure), and `diff`ed it with the grabbed icon now, and they end up
being the exact same file. So there's literally no difference.
The only other consideration is that LoadImage needs to be exported,
since it's implemented in GraphicsResources.cpp. I just opted to
forward-declare it right before LoadIcon in Screen.cpp, since it's
really the only other time it's used. No need to create a new header
file for it or anything.
This is just to simplify the function. I really don't see any point in
taking away the alpha for some images, other than to disappoint people
who mod the game assets. It just complicates loading the image with no
real gain. To reduce maintenance costs, let's remove this alternate code
path.
Also it's a default argument and I don't like default arguments.
This argument... doesn't do anything.
First off, setting it to true explicitly enables blending on the
resulting surface, which is kind of the exact opposite of the variable
name and is misleading to say the least? And secondly, SDL surfaces have
blending enabled by default anyways, so it still doesn't even do
anything.
It's also a default argument, and I'm not one to shy away from removing
such default arguments.
This includes:
- Removing the constructor in favor of actually being able to see that
there's an actual function called being made initializing the struct
- Removing the use of a reference in Screen::init() in favor of using a
pointer
- Adding the struct qualifier everywhere (it's not much typing),
although technically you could typedef it in C, but I'd rather much
not typedef just to remove a tag qualifier
I know earlier I removed the gameScreen extern in favor of using
screenbuffer, but that was only to be consistent. After further
consideration, I have found that it's actually really stupid.
There's no reason to be accessing it through screenbuffer, and it's
probably an artifact of 2.0-2.2 passing stack-allocated otherwise-global
classes everywhere through function arguments. Also, it leads to stupid
bugs where screenbuffer could potentially be NULL, which has already
resulted in various annoying crashes in the past. Although those could
be fixed by simply initializing screenbuffer at the very top of main(),
but, why not just scrap the whole thing anyway?
So that's what I'm doing.
As a nice side effect, I've removed the transitive include of Screen.h
from Graphics.h. This could've been done already since it only includes
it for the pointer anyway, but it's still good to do it now.
In aa7b63fa5f, I didn't notice that the
result was implicitly being converted to int by the min/max from before.
I instead added it to the existing char, but that resulted in a char
overflow (it's unsigned, so thankfully not undefined behavior).
But of course the entire point of that commit is to make it explicitly
clear when you are converting between types, intentionally or otherwise,
in min/max comparisons. So despite causing a regression (which I have
now fixed), at least it did its job.
It's been long overdue that this variable be named properly. 2.2 added
integer scaling mode (thanks Ethan), 2.3 renamed it to scaling mode. Now
2.4 will properly call it what it is so people won't be confused by it.
The ScreenSettings struct member is renamed from stretch to scalingMode
along with the Screen class member being renamed, as well as the
toggleStretchMode function being renamed to toggleScalingMode as well.
Unfortunately, due to compatibility, we can't change the <stretch> XML
tag.
VVV_min/max are functions that only operate on ints, and SDL_min/max are
macros that operate on any type but double-evaluate everything.
I know I more-or-less said earlier that SDL_min/max were dumb but I've
changed my mind and think it's better to use them, taking care to make
sure you don't double-evaluate, rather than trying to generate your own
litany of functions with either your own hand-rolled generation macros,
C++ templates, C11 generics, or GCC extensions (that last one you'd
technically use in a macro but it doesn't really matter), all of which
have more downsides than just not double-evaluating.
And the upside of not double-evaluating is that you're disencouraged
from having really complicated single-line min/max expressions and
encouraged to precompute the values beforehand anyway so the final
min/max is more readable. And furthermore you'll notice when you
yourself end up doing double-evaluations anyway. I removed a couple
instances of Graphics::len() being double-evaluated in this commit (as
well as cleaned up some other min/max-using code). Although the only
downside to those double-evaluations was unnecessary computation,
rather than checking the wrong result or having multiple side effects,
thankfully, it's still good to minimize double-evaluations where
possible.
The reason why the wall stuck flipping behavior happened in the first
place was because the code went like this:
if (jumppressed)
{
if (onground && gravitycontrol == 0)
{
gravitycontrol = 1;
}
if (onroof && gravitycontrol == 1)
{
gravitycontrol = 0;
}
}
Basically, if you were both on ground and on a roof (i.e. stuck in a
wall), you would flip, but then due to code order and the fact that the
statement is not connected to the previous one, you would immediately
unflip afterwards. But if you were already flipped then the only path
that can be taken is to unflip you, since it's the statement that
appears last.
52fceb3f69 replaces the onground/onroof
conditionals with any_onground/any_onroof, so any player entity would
allow you to flip. But otherwise the code is the same. So is that the
problem?
No; tracing it through with GDB reveals that when you flip,
gravitycontrol is being set to 1, but never being set to 0. And it turns
out that's because any_onroof is not getting set. And that happens
because of another thing that 52fceb3f69
did - which was to set any_onground/any_onroof to true if indeed any
player entity was on ground or on a roof.
Unfortunately, the way Leo did it was to make the two statements
mutually exclusive - an 'if'-'else if' instead of two separate
statements. So a single entity could not mark both any_onground and
any_onroof as true (and the majority of the time, you will be a single
entity).
Thus, the solution is to just drop that 'else'.
Fixes#855.
I noticed when going frame-by-frame in Vertigo that sometimes the
wrapping enemies at the top sometimes just "popped" in frame. This is
because the sprite warp code only draws the warping sprite of sprites at
the bottom of the screen if they're below y=210. However, the warp point
starts at y=232, and warp sprites can be at most 32x32, which is exactly
the case with the Vertigo sprites, which are exactly 32x32. So the warp
code should start warping sprites if they're below y=200 (232 - 32)
instead.
Horizontal warping also has this problem; it warps at x=320 and
starts drawing warp sprites at x=300, even though it should start
drawing at x=288 (320 - 32). I've gone ahead and fixed that as well.
This is just in case the background gets changed by a custom level or
something to be something that would otherwise result in bad contrast.
Also if it needs to go outside the box for some reason. And I just like
the look of the outline.
Whew, look at all those copy-pasted print statements!
Doing this because of the in-game timer feature. The text would
otherwise clash harshly with the timer otherwise. Even with the outline
it still clashes, but at least there's an outline so it's not as harsh.
This adds centiseconds to the in-game timer, as well as the time trial
timer.
This is to aid speedrun moderators in determining when exactly a run was
completed, which they can't easily do if the timer only has a precision
up to a second.
The problem was that it also needed to check that game.swnmode was true,
in addition to game.swngame being 1, to actually check that the Super
Gravitron was being played.
Currently, all game-gamestate variables are just ints. This is not
particularly type-safe, in case the number of enums changes. To verify
that all current uses of the game-gamestate variables actually use the
enums, change them to be typed with the enum instead.
(As an aside, we should probably rename this so that it can't be
confused with Terry's state machine that has several different ways to
exploit to warp you to the credits, but that's something to do later.)
You'll note that getting in to the glitchy state of the game (the state
where you could play the game after it had hardreset() called on it)
required the player to quit to menu with ingame_titlemode set to true.
Well, quitting to menu calls hardreset(). So if hardreset() is called
when quitting, then you can no longer preserve ingame_titlemode that
way. This is a bit overkill, but I'm just taking precautions.
The game will now assert if the main menu is created while
ingame_titlemode is true, or if we attempt to load into a mode while
it's true. And if assertions are disabled then it just stops doing it
anyway.
I don't think there's any way to get a glitched ingame_titlemode again,
ever since I removed save data deletion taking you back to the main
menu. But I've had enough bugs with the fact that we more-or-less use
the same state for main menu options and in-game options, and that
glitched ingame_titlemode bug DID just happen, so I'm taking
precautions.
The next commit will add logic that more-or-less quits the whole block
if ingame_titlemode, and instead of adding another layer of indentation
I will just pull this into its own function so we can use a return
statement.
While I was testing deleting data while you were in-game, I noticed that
deleting data gave you all the "Win with less than X deaths" trophies,
even if you never got any of them before deleting data. Well, it turns
out that if you have the best game death count of 0, then you win every
trophy, and if you have the best game death count of -1 then that means
you haven't completed the game yet.
This reset was added in e3bfc79d4a, so at
least it's not in 2.3, but I only have myself to blame for making this
mistake. Whoops.
Going back to the main menu allowed for glitchiness to occur if you
deleted your save data while in in-game options. This meant you could
then load back in to the game, and then quit to the menu, then open the
options and then jump back in-game, exploring the state of the game
after hardreset() had been called on it. Which is: pretty glitchy.
For example, this meant having your room coordinates be 0,0 (which is
different from 100,100, which is the actual 0,0, thanks for the
100-indexing Terry), which caused some of the room transitions to be
disabled because room transitions were disabled if the
game.door_up/down/left/right variables were -2 or less, and they were
computed based on room coordinates, which meant some of them went
negative if you were 0,0 and not 100,100. At least this was the case
until I removed those variables for, at best, doing nothing, and at
worst, being actively harmful.
Anyways, so deleting your save data now just takes you back to the
previous menu, much like deleting custom level data does. I don't know
why deleting save data put you back on the main menu in the first place.
It's not like the options menu needed to be reloaded or anything. I
checked and this was the behavior in 2.0 as well, so it was probably
added for a dumb reason.
I considered prohibiting data deletion if you were ingame_titlemode, but
as of the moment it seems to be okay (if albeit weird, e.g. returning to
menu while in Secret Lab doesn't place your cursor on the "play"
button), and I can always add such a prohibition later if it was really
causing problems. Can't think of anything bad off of the top of my head,
though.
Btw thanks to Elomavi for discovering that you could do this glitch.
Okay, so, this is the elephant sprite, right?
https://i.imgur.com/dtS70zk.png
This is how it looks in the actual game, when you stitch all the rooms
together:
https://i.imgur.com/aztVnFT.png
Looks kind of messed-up, doesn't it?
Okay, so, in the bottom two rooms (11,9) and (12,9), the elephant is
placed at y-position -152. But in (11,8) and (12,8), it's placed at
y-position 96. This is despite the fact that -152 plus 240 is 88, not
96.
Similarly, in the left two rooms (11,8) and (11,9), the elephant is
placed at x-position 64, but in the right two rooms (12,8) and (12,9),
the elephant is placed at -264. This is despite the fact that 64 minus
320 is -256, not -264.
All of this stems from the calculations in Otherlevel.cpp using offsets
of -248 and -328 instead of -240 and -320.
So there's an 8-pixel offset that causes the elephant to be chopped off
when viewed with all the rooms stitched together. Simple enough to fix.
For the y-position fixes, I decremented the initial 8-pixel multiplier
as well, else the elephant would sink into the floor.
And this is what the elephant looks like now after stitching:
https://i.imgur.com/27ePLm1.png
Thanks to Tzann for pointing this out.
These warnings are kinda spammy, and they make sense in principle.
vlog_error takes a format string, so passing it an arbitrary string
(even error messages from libraries) isn't a good idea.
Dvoid from Discord just reported a crash when trying to load a
custom tiles2.png that was encoded weirdly.
The problem is that we don't check the return value from LodePNG, so
LodePNG gives us a null pointer, and then
SDL_CreateRGBSurfaceWithFormatFrom doesn't check this null pointer,
which then propagates until we crash in SDL_ConvertSurfaceFormat (or
rather, one of its sub-functions), and we would probably crash somewhere
else anyway if it continued.
After properly checking LodePNG's return value, along with printing the
error, it turns out that Dvoid's custom tiles2.png had an "invalid CRC".
I don't know what this means but it sounds worrying. `feh` can read the
file correctly but it also reports a "CRC error".
While we can't fix Canonical, we can at least work around them, and help
people on Ubuntu out by linking them to my comment listing the
currently-known workarounds.
SDL_GetTicks64() is a function that got added in SDL 2.0.18, which is
just an SDL_GetTicks() without a value that wraps every ~49 days,
instead wrapping after the sun explodes and kills us all. Oh sorry,
didn't mean to get existential.
For now, put this behind an SDL_VERSION_ATLEAST guard, which will be
removed when SDL 2.0.18 officially releases and we can update to it.
My latest rebase of #624 (refactoring/splitting editor.cpp) accidentally
overwrote #787 and essentially reverted it entirely. So, add it back in.
This is the same as #787 except it uses the new names, uses SDL_INLINE
to inline the function, and uses named constants.
GCC warns on casting `void*` to function pointers. This is because the C
standard makes a clear distinction between pointers to objects (`void*`)
and pointers to functions (function pointers), and does not specify
anything related to being able to cast object pointers to function
pointers.
The warning message is factually wrong, though - it states that it is
forbidden by ISO C, when in fact it is not, and is actually just
unspecified.
We can't get rid of the cast entirely, because we need the explicit cast
(the C standard _does_ mandate you need an explicit cast when converting
between object pointers and function pointers), and at the end of the
day, this is simply how `SDL_LoadFunction()` works (and more
importantly, how `dlsym()` works), so we can't get rid of it, and we
have no reason to anyways since it means we don't have a hard runtime
dependency on Steam (unlike some other games) and casting `void*` to
function pointers always behaves well on every single platform we ship
on that supports Steam.
Unfortunately, this warning seems to be a part of -Wpedantic, and
there's no way to disable this warning specifically without disabling
-Wpedantic. Luckily, I've found a workaround - just cast to `intptr_t`
before casting to the function pointer. Hopefully the compiler doesn't
get smarter in the future and this ends up breaking or anything...
* Add `setactivityposition(x,y)`, add new textbox color `transparent`
This commit adds a new internal command as a part of the visual activity zone changes I've been making.
This one allows the user to reposition the activity zone to anywhere on the screen.
In addition, this commit adds the textbox color `transparent`, which just sets r, g and b to 0.
rgb(0, 0, 0) normally creates the color black, however in VVVVVV textboxes, it makes the background
of them invisible, and makes the text the off-white color which the game uses elsewhere.
* add new variables to hardreset
* Fix unwanted text centering; offset position by 16, 4
It makes sense for `setactivityposition(0, 0)` to place the activity zone in the default position,
so the x has been offset by 16, and the y has been offset by 4.
Text was being automatically centered, meaning any activity zone which wasn't centered had misplaced text.
This has been fixed by calculating the center manually, and offsetting it by the passed value.
In previous versions, the game mistakenly checked the wrong color
channel of sprites, checking the red channel instead of the alpha
channel. I abuse this in some of my levels. Then I broke it when
refactoring masks so the game now no longer checks the red channel but
seems to check the blue channel instead. So re-fix this to the previous
bug, and preserve the previous bug with a comment explaining why.
This broke when I was refactoring things earlier, because we no longer
have a direct reference to the contents array, instead using a copied
int. But we have a settile() function anyway, so why not use it?
It is impossible to get on the quicksave screen in time trials, because
Enter is always bound to restarting time trials in a time trial, and
there's no way to open the map screen otherwise.
So, I've decided to add a fun little message in case someone somehow
manages to get to this screen in a time trial.
As is typical, the code was copy-pasted to account for Flip Mode, and
then copy-pasted again to account for custom levels, leading to four
instances of the same code.
I clean this up while also improving code style. This is where the new
FLIP macro and the fixed PrintWrap help a lot - otherwise the "Game
saved ok!" screen would look really wrong without the height
corrections.
It now looks more like the FLIP macro in Render.cpp: The y-position is
simply the height of the area the object is being flipped in, minus the
y-position itself, minus the height of the object. So:
flipped_yp = constant - yp - height
This is just a mathematical simplification of the existing statement,
which is:
flipped_yp = yp + 2 * (constant/2 - yp) - height
Using algebra, the 2 distributes into the parentheses, so
flipped_yp = yp + constant - 2 * yp - height
And the two `yp`s add together, so
flipped_yp = constant - yp - height
It's more readable this way.
Also I am using a named constant instead of a hardcoded one.
Otherwise, the text will be in the wrong position compared to normal
mode.
PrintWrap is not used in Flip Mode yet, but it will be used on the map
screen in an upcoming change of mine. The FLIP macro in Render.cpp can't
help us there, since it would need to know the height of the wrapped
text at compile time, when the height is only figured out at runtime
based off of the string (or, well, right _now_ the string _is_ known,
but we are going to merge localization for 2.4, and it's better to
future-proof...), and only PrintWrap itself can figure out the height of
the text. (Or, well, I suppose you could call it from outside the
function, but that's not very separation-of-concernsy style.)
Flipping objects in Flip Mode needs to account for the heights of those
objects (that's why flipme text boxes in Flip Mode in 2.2 were
positioned wrongly).
Also, turn it into a macro instead of an inline function.
This changes the positions of all existing de-duplicated map menu text
in Flip Mode, but it'll be more correct.
I misread SDL's code and thought that SDL's `begin_code.h` was internal
only to SDL. It turns out you get it when you include basically any
header, such as `SDL_stdinc.h`. So use it directly instead of copying it
for our own.
Between accounting for Flip Mode and custom levels, this code was
copy-pasted three times, leading to _four_ instances of one code!
Anyways, I've cleaned it up. The position of the text in Flip Mode is
going to differ by 4 pixels from how it was previously, but that really
shouldn't matter.
While dying in No Death Mode was fixed to no longer say "One trinkets"
in 2.3, if you win in No Death Mode with one trinket, the game would say
"One trinkets".
So to fix this, just slot a ternary in there. The code is already kind
of bad anyways and is going to be refactored/de-STLed in the future
regardless, so I'm not feeling too badly about shoving a ternary in
there like that.
This macro is to make it so it won't be error-prone to write the
semi-confusing `(a % b + b) % b` statement, and you can just use an easy
macro instead.
Currently, the only places a positive modulo is needed is when switching
tilesets, enemies, and warp directions in the editor, as well as when
getting a tile in the tower, since towers just repeat themselves
vertically. Towers used this weird while-loop to sort of emulate a
modulo, which isn't half-bad, but is unnecessary, and I don't think any
compiler would recognize it as a modulo. (And if it's not optimized to a
proper modulo... what happens if the number being moduloed is really,
really big?)
Believe it or not, there are still some remnants of the ActionScript
coding standards in the codebase! And one of them sometimes pops up
whenever an integer division happens.
As it so happens, it seems like division in ActionScript automatically
produces a decimal number. So to prevent that, the game sometimes
subtracts off the remainder of the number to be divided before
performing the division on it.
Thus, we get statements that look like
(a - (a % b)) / b
And probably more parentheses surrounding it too, since it would be
copy-pasted into yet another larger expression, because of course it
would.
`(a % b)` here is subtracting the remainder of `a` divided by `b`, using
the modulo operator, before it gets divided by `b`. Thus, the number
will always be divisible by `b`, so dividing it will mathematically not
produce a decimal number.
Needless to say, this is unnecessary, and very unreadable. In fact, when
I saw these for the first time, I thought they were overcomplicated
_modulos_, _not_ integer division! In C and C++, dividing an integer by
an integer will always result in an integer, so there's no need to do
all this runaround just to divide two integers.
To find all of these, I used the command
rg --pcre2 '(.+?).+?-.+?(?=\1).+?%.+?([\d]+?).+?\/.+?(?=\2)'
which basically matches expressions of the form 'a - a % b / b', where
'a' and 'b' are identical and there could be any characters in the
spaces.
There's really no need to put the y-multiplication in a lookup table.
The compiler will optimize the multiplication better than putting it in
a lookup table will.
To improve readability and to hardcode things less, the new
SCREEN_WIDTH_TILES and SCREEN_HEIGHT_TILES constant names are used, as
well as adding a new TILE_IDX macro to calculate the index of a tile in
a concatenated-rows (row-major in formal parlance) array. Also, tile
numbers are stored in a temporary variable to improve readability as
well (no more copy-pasting `contents[i + vmult[j]]` over and over
again).
There's really no reason for this simple multiplication plus division to
be in a lookup table. The compiler will optimize it faster than putting
it in a lookup table will, I'm sure.
This comment was referring to a now-deleted variable named mkdirResult
that was binary-"or"ed with all mkdir() results... except for the saves
directory. That variable was only used for save file migration, which is
now axed, so this comment is referring to nothing now.
I don't really know the answer to Ethan's question, but it doesn't
matter now.
So, it turns out freeing everything in binaryBlob::clear() without
checking for NULL results in an abort() because clear() gets called on
musicWriteBlob after it attempts to write the compiled music. It's just
that no one's using VVV_COMPILEMUSIC, so no one's ran into this.
I'm keeping VVV_COMPILEMUSIC around so in the future people can compile
music directly from the game (and probably half the existing
VVV_COMPILEMUSIC code is going to be thrown out, but oh well).
Since this refers to specific exported file data, let's make sure this
is portable. I'm not sure if we'll ever ship on systems where
sizeof(int) != 4 or sizeof(bool) != 1, but better to be safer and
future-proof than not.
This variable is only used when compiling music. Since it doesn't
actually keep track of the number of headers otherwise, ifdef it behind
VVV_COMPILEMUSIC.
2.3 introduced a regression with destroy(platforms). The problem was
that isplatform wasn't being set to false when the entity got disabled,
so if the platform was moving, it would keep moving until it hit a wall,
instead of stopping immediately.
I have just built a new CentOS container based off of the updated
Dockerfile, which now has SDL 2.0.16 installed. This updates the CI to
point to the new container.
Previously, loading STDIN used std::istreambuf_iterator and std::vector
and whatnot because... I guess it was less typing? But this isn't 1989;
we have the disk space to spare and we don't need to use fancy stuff
just to save on typing. It's not that hard to implement an array that
regrows to the nearest power of two every time.
All system header includes should come before project-specific includes
(includes specific to this game), while coming after the include
specific to the given file (if any; main.cpp doesn't have any).
These are unused.
Ethan originally added them in case Terry wanted achievement
percentages. But he didn't add them, and I don't think the achievements
are changing anytime soon, so it's safe to remove this dead code.
If the string is hardcoded, then use compile-time string literal
concatenation instead.
I don't know if compilers are smart enough to recognize when you're
passing in hardcoded strings and to concatenate them into the string
literal at compile time instead. I also don't know that if compilers are
smart enough to recognize that, that further they recognize all the
logging functions are just wrappers around printf, and so they can
perform the same optimization at those function call sites, too. So it's
better to just do the string concatenation explicitly instead.
Instead of having three separate copies of the function list, use macro
magic to make it so there is only one list that we use in three
different cases.
SDL just got an API to toggle VSync without having to tear down the
renderer ( libsdl-org/SDL#4157 ). We can remove the workaround and use
that instead. For now, we are putting it behind an ifdef until SDL
2.0.18 officially releases in November.
Fixes#831.
Constants.h will house constants like the screen size and others. But
basically only the screen size for now.
Now we don't have to type that "4 bytes per 40 chars (whole screen)"
comment everywhere...
Since those are all downstream recipients of either static storage or
memory that doesn't move for the duration of the custom level, it's okay
to make these be `const char*`s without having to redo any of the RAII
memory management.
mapclass::currentarea() is included in this as well. I also cleaned up
Tower.cpp's headers to fix some transitive includes because I was
removing UtilityClass.h includes from all other level files too.
The "Untitled room" names no longer show any coordinates, because doing
so would require complicated memory management that's completely
unneeded. No one will ever see them, and if they do they already know
they have a problem anyway. The only time they might be able to see them
is if they corrupted the areamap, but this was only possible in 2.2 and
previous by dying outside the room deaths array in Outside Dimension
VVVVVV, which has since been patched out. Besides, sometimes the
"Untitled room" gets overwritten by something else anyway (especially in
Finalclass.cpp), so it really, really doesn't matter.
There's no reason it needs to be an std::string here.
Although, realistically, we should be using an enum instead of
string-typing, but, eh, that can be fixed later.
Companions would not spawn if you didn't load the current room via a
room transition. This meant that companions wouldn't spawn if you loaded
a save file with a companion, at least not until you moved to a
different room and triggered a screen transition. But most importantly,
it meant that the Intermission 1 supercrewmate would never spawn,
because going to Intermission 1 does a straight gotoroom, and does not
do a room transition.
Turns out the roomchange refactor broke things, because of course it
did. The companion logic was implicitly relying on that bool to be set,
because...? Either way, it doesn't make sense. Using roomchange implied
that the code wanted to be ran only when doing a room transition, which
is clearly not the case here. The best thing to do here is to just move
it to a separate function that gets called at the end of
mapclass::gotoroom().
So, I ended up breaking supercrewmate spawning with that roomchange
refactor. However, upon investigating how to fix it, I was running into
a weird interpolation issue due to scmmoveme, as well as the companion
spawning in the ground in "Very Good". And I was wondering why I or no
one else ended up running into them.
Well, as it turns out, scmmoveme ends up doing absolutely nothing. There
are only two instances where scmmoveme is used. The first is if you
respawn in "Very Good", and somehow have your scmprogress set to that
room. But that's impossible, because whenever you respawn, your
scmprogress is always set to the one after the room you respawn in. Even
if you respawned in the room previous to "Very Good" (which is "Don't
Get Ahead of Yourself!"), it still wouldn't work, since the logic always
kicks in when a gotoroom happens, and not only when a supercrewmate is
actually spawned. Since the scmprogress doesn't match, that case never
gets triggered, and we get to the second time scmmoveme is used, which
is in the catch-all case that always executes.
This second instance... also does nothing, because since we just
respawned, and our scmprogress got set to the room ahead of us, there is
no supercrewmate on screen. Then getscm() returns 0, and the player is
always indice 0, so the only thing we end up doing is setting the
player's x-position to their own x-position. Brilliant.
Anyway, this code results in interpolation issues and the supercrewmate
spawning in the ground on "Very Good" if you die, when my fix is
applied, because my fix moves this logic around to a different frame
order, and that actually ends up making scmmoveme no longer dead code.
So to recap: we have dead code, which looks like it does something, but
doesn't. But if you move it around in a certain way, it ends up having
harmful effects. One of the joys of working on this game...
It's also hilarious that it gets saved to the save file. Why? The only
time this variable is true, it is for literally less than a frame,
because it always gets set to false, because you always respawn using a
gotoroom whenever the supercrewmate dies, because you never respawn in
the same room as a supercrewmate, because Intermission 1 was
deliberately designed that way (else you'd keep continually dying since
the supercrewmate wouldn't move out of the way).
These were bfont_rect, bg_rect, foot_rect, and images_rect.
bg_rect was only used once to draw the ghost buffer in the editor, but
that was only because Ally didn't know you could just pass NULL in, cuz
the ghost buffer is the same size as the backbuffer.
RGBflip() does the exact same thing as getRGB(), now that all the
surface masks have been fixed. This axes RGBflip() and changes all
callers to use getRGB() instead. It is more readable that way.
By doing this, there is less copy-pasting. Additionally, it is now
easier to search for RGBf() - which is an ENTIRELY different function
than RGBflip() - now that the name of RGBf is no longer the first four
characters of some different, unrelated function. Previously I would've
had to do `rg 'RGBf[^\w]'` which was stupid and awful and I hated it.
Turns out, the r, g, and b arguments don't actually do anything!
There was a call to RGBf() in the function. RGBf() is just getRGB() but
first adds 128 and then divides by 3 to each of the color channels
beforehand. Unfortunately, RGBf() does not have any side effects, and
the function threw away the return value. Bravo.
This also reveals that the website images drawn in the credits in the
main menu are only recolored because of a stale `ct` set by the previous
graphics.bigprint(), and not because any color values were passed in to
drawimagecol()... What fun surprises the game has in store for me every
day.
This fixes a regression where entering playtesting while a track was
fading out (by exiting out of playtesting with a track playing and then
immediately entering back in with the level start music set) would
result in no music.
The cause is the game doing fades even though nothing is playing, which
puts it in a confusing state.
This wrapper function is for (a) future-proofing (b) proactive
prevention of future copy-pasting (c) to clarify that we never actually
halt music in the SDL_mixer sense, we only pause it, so to check if the
music is halted we actually check if the music is paused instead. This
is important because Mix_PlayingMusic() does not check if the music is
paused and Mix_PausedMusic() does not check if the music is halted.
When you're on the music changing screen in the editor, it plays the
current track. When you return, it stops playing the track. However, if
you press escape, it doesn't stop playing the track. This is because
pressing escape just returns to the previous menu without stopping
playing the track.
To fix this, I just added some kludge in the return menu function. This
is kinda super bad but it works for now and is just something to clean
up later. Maybe like each menu having exit callbacks or something, I
dunno.
This is kinda a regression, kinda sorta not. In 2.2 and previous,
pressing escape would just close the settings menu entirely, which also
bypassed the music fadeout. 2.3 made it so pressing escape doesn't
entirely close the settings menu, and just returns to the previous menu,
which fails in a different way. But the intended way is definitely to
select the return option and having the music fade out.
This function now properly deletes the Super Gravitron record, the Super
Gravitron rank, and the best game deaths. They were not being properly
reset previously, meaning you would have to go into your save file to
properly clean out your save data.
If the map size was less than 20x20, platv values outside the map would
end up being saved as 67372036.
This happens because SDL_memset() operates on the byte level, and not
the multi-byte level. So it takes only the lower 8 bits of 4 and repeats
it for each byte in each integer, creating 67372036.
This was done in 2.2 and previous probably to fix the fact that there
were multiple conflicting audio controls (the player wants to mute the
audio but the game wants to fade in the audio), but is now actively
harmful since 2.3, because muting the game while finishing the
completion prompt means the music will never come back in, even after
unmuting.
I also notice that when collecting a custom crewmate, the game checks
for the level's start music instead of if there's actually a current
song playing right now. I don't know why this was done, because it
would've been better to copy-paste the trinket collection logic here.
It's entirely possible for the audio to just be muted and never come
back if the level has no start music but plays a song by using a script.
Anyways, leaving it alone because it's quite possible that a level might
be intentionally designed around this, I can't really tell the
intentions of every level creator, and it's easy to work around (either
don't use custom crewmates, which every modern level basically does
nowadays, or just set the start music).
For some reason, when completing a custom level and fading to the menu,
the game attempts to fade the music in and also fade the music out at
the same time. This results in nothing happening at all, and in 2.2 and
previous, results in audio fading out from max volume while the game is
frozen on a black screen after the fadeout.
To avoid any potential badness, just remove these.
In the main game, if you press R during the trinket collection prompt
after collecting a trinket, AND you have never entered Comms Relay, and
you respawn in a different room, the trinket collection gamestate will
be interrupted, but you will still be left with the advance text prompt,
cutscene bars, and muted music.
The previous workaround to fix the music would be to mute and then
unmute the game, but due to the new music changes, this workaround
(which in and of itself is a bug) no longer works. Instead, the music
would have to be restarted by going into another zone on the map.
Having an advance text prompt outside of a cutscene results in the
player being unable to flip, but they can still move around left and
right.
Speedrunners previously used the no-Comms-Relay interrupting behavior to
skip certain trinket collection prompts entirely with a frame-perfect R
press, so I can't patch that out. Having an advance text prompt outside
of a cutscene is (ab)used in custom levels to intentionally prevent the
player from flipping, and furthermore, it's also used in credits warp
runs of the main game to increment the gamestate; so I cannot patch that
out. The ability to press R everywhere even during cutscenes was added
for good reason - to make it less likely that a softlock can happen - so
I don't want to revert it.
But I still think this is worth fixing because previously, the
punishment for missing the frame-perfect window late was simply not
skipping the trinket prompt (since the R-press would be ignored), but
now the punishment is basically having to reset because of the advance
text prompt.
I would usually handle this in gamestate 0, but awful custom levels
might want to intentionally interrupt the gamestate to do, I don't know,
something. No level does that so far, but I'd like to do the least
invasive thing.
So what I've done is made it so the effects of interruption are undone
if you press R and the gamestate is interrupted. This is handled in
mapclass::resetplayer().
Without this, `fixedloop` will loop infinitely until focus is regained.
However, Emscripten won't actually know that focus is regained until
`fixedloop` returns.
getBGR, when used in FillRect, was actually passing colors in RGB order.
But now the masks are fixed, so remove it, and fix up all existing
getBGR colors to use getRGB instead.
Due to the mask inconsistencies, getRGB calls that were passed to
FillRect ended up actually being passed in BGR order. But now that the
masks are fixed, all these BGR colors look wrong. So, fix up all of them
(...that's a _lot_ of copy-pasted code...) to be passed in RGB order.
This fixes the color ordering of every SDL_Surface in the game.
Basically, images need to be loaded in ABGR format (except if they don't
have alpha, then you use RGB? I'm not sure what's going on here), and
then they will be converted to RGB/RGBA afterwards.
Due to the surfaces actually being BGR/BGRA, the game used to use
getRGBA/getRGB to swap the colors back around to BGRA/BGR, but I've
fixed those too.
If it's at all possible to use `const std::string&` when passing
`std::string`s around, then we use it. This is to limit the amount of
memory usage as a result of the frequent use of `std::string`s, so the
game no longer unnecessarily copies strings when it doesn't need to.
I've made a new function, Graphics::do_print(), that does the actual
text printing itself. All the interfaces of the other functions have
been left alone, but now just call do_print() instead.
I also removed PrintOffAlpha() and just calculated the center x-position
in bprintalpha() itself (like bigbprint() does) to make it easier to
de-duplicate code.
Text boxes have `r`, `g`, and `b`, and `tr`, `tg`, and `tb`. `tr`, `tg`,
and `tb` are the real colors of the text box, and `r`, `g`, and `b` are
merely the colors of the text box as the text box's alpha value is
applied to them.
Compare this with, say, activity zones (which are drawn like text boxes
but aren't text boxes): There is `activity_r`, `activity_g`, and
`activity_b`, and when they're drawn they're all multiplied by
`act_alpha`.
So just do the same thing here. Ditch the `tr`, `tg`, and `tb`
variables, and make `r`, `g`, and `b` the new `tr`, `tg`, and `tb`
variables. That way, there's simply less state to have to update
separately. So we can get rid of `textboxclass::setcol()` as well.
This is a variable that's only used in one method, and it's always
initialized beforehand. No need to carry it around, taking up memory,
and making code analysis more complicated.
All parameters are now made const, to aid in the reader in knowing that
they aren't ever changed.
Useless comments have been removed and been replaced with helpful
comments.
Useless parentheses have been removed.
Spacing has been made consistent.
Declarations and code are no longer mixed.
I'm honestly not too sure why drawcustompixeltextbox ever existed? All
it seemed to do was draw even more horizontal/vertical tiles to finish
any gaps in the tiling... which was all completely unnecessary and
wasteful, because even the previous drawpixeltextbox implementation
covered all gaps in all custom level map sizes that I tried.
Anyway, that at least gets rid of one copy-pasted function.
This draws the remaining horizontal/vertical tile just beside the final
corner if the width/height is not a multiple of 8. (It'd be wasteful to
draw it if the width/height was a perfect multiple of 8, and result in
double-drawing translucent pixels if there were any.)
This has an advantage over the previous system of shifting the
horizontal/vertical tiling, in that custom corner textures don't look
weird due to overlapping like this. Now, custom horizontal/vertical
tiles _can_ look weird if they don't completely tile correctly (or if
they have translucent pixels), but that's better than mucking up the
corners.
`w` and `h` are provided alongside `w2` and `h2`. `w2` and `h2` are in
blocks of 8, while `w` and `h` are in pixels. Therefore, `w2` and `h2`
can just be figured out by diving `w` and `h` by 8.
Also, `xo` and `yo` were used to slide the horizontal/vertical tiling of
the text box a bit into one set of corners, so the horizontal/vertical
tiling wouldn't visibly overlap with the other corners, if using default
textures. This requires hardcoding it for each width/height of text box,
which isn't something that's generalizable. Also, it results in corners
that look weird if the corners have custom textures that don't adhere to
the same shape as default textures.
In the next commit I'll fix the non-multiple-of-8 text box dimensions
differently. Can't do it in this commit or the diff looks weird (at
least with my diff algorithm).
This fixes a bug where the player could bring up the map on the very
first frame of a gamemode(game) animation. This is because the menu
animation checked graphics.menuoffset, but graphics.menuoffset wouldn't
have changed at that point because it only set graphics.resumegamemode.
Instead, just check for graphics.resumegamemode directly. We also need
to assign it to false whenever the map is closed so the player won't be
prevented from using the map screen again.
This fixes all the headaches about map.extrarow having to be the correct
value and which way it should be and whatnot. The latest headache was
the detection that prevent user-initiated menu animations while an
animation was already happening being tripped because
graphics.menuoffset would be 230 (due to closing the menu while being in
a room without a room name), but then going to a room with a room name
would check for 240 instead, and 230 is less than 240. (The numbers are
the wrong way round because I got the ternaries the wrong way round, but
even if the numbers are the correct way round, the bug would still
happen, but it would just be reversed.)
So instead, I've just made it 240 for both. This doesn't change the
duration of the menu animation (because the animation moves in
increments of 25, and 230 / 25 == 240 / 25 under integer division). It
might change the animation slightly, but it was already inconsistent
anyway because map.extrarow was always set to be 1 in custom levels, and
I legitimately would not be able to tell the difference without
recording the animations and nitpicking it frame-by-frame.
Fixes#841.
The player gets kicked out of the Super Gravitron if they have
invincibility or slowdown enabled. However, this can be confusing if no
message pops up
( 3039355280/ )
. So I've made it so that a text box will pop up when they get kicked
out.
This makes it so gamemode(teleporter) will always do an animation, even
if the game is already in TELEPORTERMODE.
I used this script to test:
gamemode(teleporter)
delay(5)
gamemode(teleporter)
delay(5)
gamemode(teleporter)
In 2.2, this script starts the map menu bringing-up animation three
times.
In previous 2.3, this script starts the map menu bringing-up animation
once, but then the next gamemode(teleporter) immediately finishes the
animation, and the third gamemode(teleporter) does nothing.
This commit restores it to 2.2 behavior.
This makes it so it's not even possible to stay on the TELEPORTERMODE
screen by opening the map while it's being brought down. It also makes
it so the map animation is able to be canceled when being brought up
just by opening the map and closing it.
Fixes#833.
This restores it to 2.2 behavior, where the cutscene bars timer also
ticked in TELEPORTERMODE. It was a 2.3 regression that the cutscene bars
timer didn't tick there.
This makes it so if you manage to get stuck in TELEPORTERMODE when a
cutscene ends, the cutscene won't be stuck on untilbars() waiting for
the cutscene bars to go away, since the cutscene bars timer now ticks.
Also, add a sync parameter to avoid calling syncfs too often.
Calling syncfs twice in a row is both inefficient and leads to errors
displaying twice. This allows us to bypass it when saving unlock.vvv as
part of savestatsandsettings.
This object basically had no reason to exist... it was just more verbose
to use, which really reminded me of Java. Anyway, this is the last thing
named after the editor for no reason when it should be a part of the
customlevelclass, so I moved its attributes to customlevelclass.
This fixes the fact that the name of the singular type is plural, but
the name of the plural array is singular. Which has always annoyed me,
too. Also this makes it more clear that custom entities don't have much
to do with the editor.
That's what it is - it's an entity in a custom level. Not something to
do with the editor, necessarily. Like before, the name of the XML
element will remain the same.
That's what edlevelclass is... so that's what it should be named. (Also
removes that "ed", too, making this less coupled to the in-game editor.)
Unfortunately, for compatibility reasons, the name of the XML element
will still remain the same.
CustomLevels.h now uses 4-space indents - like all other space-indented
files - instead of 2-space indents. This has bugged me for a while and I
decided to just fix it now.
This is a pretty hefty commit! But essentially, I made a new editorclass
object, and moved all functions and variables that only get used in the
in-game level editor to that class. This cleanly demarcates which things
are in the editor and which things are just general custom level stuff.
Then I fixed up all the callers. I also fixed up some NO_CUSTOM_LEVELS
and NO_EDITOR ifdefs, too, in several places.
As far as I can tell, this function has never been implemented, and only
existed in this header file. FILESYSTEM_getLevelDirFileNames() already
exists (well, used to exist; it's been changed and renamed to
FILESYSTEM_enumerateLevelDirFileNames()), so I'm removing this now.
This accompanies the editor.cpp -> CustomLevels.cpp change; I'll be
splitting out the editor functions in the next commit. The name of the
include guard has been changed as well, but not anything else.
This moves editorrenderfixed(), editorrender(), editorinput(),
editorlogic(), and their associated functions to a new file named
Editor.cpp - which is exactly what it says on the tin; it stores all the
functions related to the actual in-game editor loop. Also, the existing
editor.cpp has been renamed to CustomLevels.cpp.
All XML functions now check the return value of
tinyxml2::XMLDocument::Error() after each document gets loaded in to
TinyXML-2. If there's an error, then all functions return. This isn't
strictly necessary, but printing the error message that TinyXML-2 is the
bare minimum we could do to be useful.
Additionally, I've standardized the error messages of missing or
corrupted XML files.
Also, the way the game went about making the XML handles was... a bit
roundabout. There were two XML handles, one for the document and one for
the root element - although only one XML handle suffices. So I've
cleaned that up too.
I could've gone further and added error checking for a whole bunch of
things (e.g. missing elements, missing attributes), but this is good
enough.
Also, if unlock.vvv or settings.vvv don't exist yet, the game is
guaranteed to no-op instead of continuing with the function. Nothing bad
seems to happen if the function continues, but the return statements
should be there anyway to clearly indicate intent.
If settings.vvv doesn't exist, loadsettings() calls savesettings(), but
savesettings() already prints a message if settings.vvv doesn't exist.
So then the output would look like
No settings.vvv found. Creating new file
No settings.vvv found
Which is clearly redundant.
The same thing happens with unlock.vvv, but in that case the following
prints instead
No unlock.vvv found. Creating new file
No Stats found. Assuming a new player
I will need to be able to return from this function if there's an XML
error, otherwise writing out the control flow manually gets really
nasty. And while I'm at it, it's some a nice de-duplication as well.
To do this, we create a temporary struct that bundles up all the
information we want for the summary, and pass it in to the intermediate
load function.
Furthermore, we can get rid of reading map.finalstretch - it affects
nothing. map.finalmode is still needed, however, because of the usage of
map.area().
Previously, Flip Mode rendering had to be complicated and allocate
another buffer to call FlipSurfaceVerticle, and it was just a mess.
Instead, why not just do SDL_RenderCopyEx, and let SDL flip the screen
for us? This ends up pretty massively simplifying the rendering code.
`-forcecolor` will force color to be on. `-nocolor` will force color to
be off.
And just because I'm a nice person, I've also added British versions of
those flags. As a treat.
This includes the bold as well.
INFO is just default, WARN is yellow, ERROR is red.
We try to automatically detect if the output is a TTY (and thus supports
colors), and don't emit colors if so. Windows 10 supports ANSI color
codes starting with a specific build, but we don't care to emit whatever
garbage Microsoft invented for builds older than that.
This is because the y-position of the graphics.onscreen() check was a
little too high. Then their name (under Beta Testing) would suddenly
disappear too early. You'd have to look real close to spot it, but it
does happen. It's cuz the credits are all kinda hardcoded, which is
probably bad, but fixing that would have to come later...
I talked with Ethan earlier about this. For 2.3, he wanted me in GitHub
contributors (well, still separate from the rest), to really highlight
the source-code-release community-driven nature of 2.3, but he said it'd
be fine to put me in C++ credits in 2.4.
The RWops stuff isn't a part of any standard PhysFS package (and given
that it explicitly wraps around SDL I'm not sure how you _would_ package
it). So we need to get the physfsrwops.h include in if
BUNDLE_DEPENDENCIES is off, otherwise this results in a compile-time
include-not-found failure.
Additionally, I've placed the PhysFS RWops stuff in their own extras/
folder, so none of the other PhysFS stuff gets included in a
-DBUNDLE_DEPENDENCIES=OFF build.
The game will freeze the player immediately if they release a
directional button within 3 frames of pressing it. Similar to flipping,
this involves global state, and will only apply to the first player
entity.
Closes#484
Flipping only applies momentum to the player entity currently being
processed. This normally wouldn't be a problem. However, flipping
involves global state, and only one flip can occur per frame. This means
that additional player entities don't get this boost of momentum, which
feels somewhat unnatural during gameplay.
This commit fixes this by splitting flip logic out of the loop over
player entities, and applying the flip momentum to all player entities.
We need to check for graphics.setflipmode, not graphics.flipmode,
because graphics.flipmode only gets assigned at the end of the frame
(due to the deferred callback). Otherwise, returning from the options
menu would always turn flag 73 on, which would make you ineligible to
get the Flip Mode trophy, even if you're in Flip Mode.
Originally this started as a "deduplicate a bunch of duplicated code in script commands" PR,
but as I was working on that, I discovered there's a lot more that needs to be done than
just deduplication.
Anything which needs a crewmate entity now calls `getcrewmanfromname(name)`, and anything which
just needs the crewmate's color calls `getcolorfromname(name)`. This was done to make sure that
everything works consistently and no copy/pasting is required. Next is the fallback; instead of
giving up and doing various things when it can't find a specific color, it now attempts to treat
the color name as an ID, and if it can't then it returns -1, where each individual command handles
that return value. This means we can keep around AEM -- a bug used in custom levels -- by not
doing anything with the return value if it's -1.
Also, for some reason, there were two `crewcolour` functions, so I stripped out the one in
entityclass and left (and modified) the one in the graphics class, since the graphics class also
has the `crewcolourreal` function.
If `setactivitytext` was the last line in a script,
the command would index the vector out of bounds.
I also modified the formatting to keep consistent
with the rest of the codebase.
These commands will change the colour and text of the next
activity zone that gets spawned. `setactivitycolour` takes all
textbox colors, and `setactivitytext` will take the text on
the next line. These commands were designed this way
to avoid breaking forwards compatibility.
When an activity zone is spawned through the
use of `createactivityzone`, and `i` is 35,
then it'll change the activity zone text to
"Press ENTER to interact".
On Emscripten, SDL_Delay is implemented as a busy loop. In addition,
everything happens on a single thread. This effectively means that
you have to let Emscripten manage the main loop, since if you do it
yourself the browser will just be frozen.
Otherwise, the new arguments to destroy(), which are 'moving' and
'disappear', would be thrown away by the simplified parser. Let's create
less work for ourselves to do and simply not have a hardcoded list of
allowed arguments for destroy() in the parser.
destroy(platforms) has been bugged since 2.0. The problem with it is
that it removes the platform entity, but doesn't remove its block. This
results in essentially turning the platorm invisible and stopping it
from moving.
This error should be fixed, but some levels (including my own) rely on
the invisible platform trick. So instead, the fixed version will be
implemented under a different name, destroy(moving).
There's also another problem with destroy(platforms), which is that the
name is misleading and it doesn't additionally destroy disappearing
platforms. I would also fix this, but in order to not run the risk of
breakage, it will have to be implemented under a different name, too. So
this will be destroy(disappear). As an added benefit, it's also more
granular to have platform-destroying functions under different names
than it is to consolidate them under the same name.
When I added the two-frame delay fix, I didn't realize that Game had a
roomchange variable that was being used as a temporary variable here.
Now that it's fully spelled out and obvious (just look at the top of
gamelogic()), I realize that the variable exists and is being used, and
other readers will realize it's being used too - so now that I know it
exists, I can axe the screen_transition variable I added in favor of
using roomchange instead.
The purpose of this variable was to keep track of if gamelogic() called
map.gotoroom() at any point during its execution. So map.gotoroom()
always unconditionally set it to true, and then gamelogic() would check
it later.
Well, there's no need to put that in a global variable and do it like
that! It makes it less clear when you do that.
So what I've done instead is made a temporary macro wrapper around
map.gotoroom() that also sets roomchange to true. I've also made it so
any attempt to use map.gotoroom() directly results in failure (and since
then using map.gotoroom() in the wrapper macro would also fail, I've had
to make a gotoroom wrapper function around map.gotoroom() so the wrapper
macro itself doesn't fail).
This is a temporary vector that only gets used in mapclass::gotoroom().
It's always guaranteed to be cleared, so it's safe to move it off.
I'm fine with using references here because, like, it's a C++ STL vector
anyway - when we switch away from the STL (which is a precondition for
moving to C), we'll be passing around raw pointers here instead, and
won't be using references here anyway.
This is a temporary variable that doesn't need to be on Game. It is
guaranteed to be initialized every time mapclass::gotoroom() gets
called, so it's safe to move it off.
2021-08-31 09:25:47 -07:00
624 changed files with 162519 additions and 76955 deletions
key:${{ runner.os }}-brew-${{ hashFiles('/usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/ninja.rb', '/usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/sdl2.rb') }}# Using hash of formula files if available, or a fixed key for simplicity if not easily determined
- name:Install dependencies
run:brew install ninja sdl2 sdl2_mixer
if:steps.cache-brew.outputs.cache-hit != 'true'
run:brew install ninja sdl2
- name:CMake configure (default version)
run:|
mkdir ${SRC_DIR_PATH}/build && cd ${SRC_DIR_PATH}/build
mkdir -p ${SRC_DIR_PATH}/build && cd ${SRC_DIR_PATH}/build
VVVVVV's source code is made available under a custom license. Basically, you can compile yourself a copy, for free, for personal use. But if you want to distribute a compiled version of the game, you might need permission first. See the [License exceptions](License%20exceptions.md) page for more information.
VVVVVV's source code is made available under a [custom license](LICENSE.md), which states that you must not distribute any materials from the game (i.e. the game's assets) which are not included in this repo unless approved by us in writing. In general, if you're interested in creating something that falls outside the license terms, get in touch with [Terry](http://distractionware.com/email/)!
There is an important general exception: if you're compiling a [Make
and Play](https://thelettervsixtim.es/makeandplay/) version of the game, then you can you freely distribute the game's assets.
The following is a list of projects which have been given permission by Terry to distribute the assets with distributions of the game, and under what conditions.
Exceptions granted to the VVVVVV source code license
-------
Last updated on November 22nd, 2020.
VVVVVV's source code is made available under a [custom license](LICENSE.md), which states that you must not distribute any materials from the game (i.e. the game's assets) which are not included in this repo unless approved by us in writing. The following is a list of projects which have been given permission by Terry to distribute the assets with distributions of the game, and under what conditions.
| Any distribution of the VVVVVV: Make and Play edition | | All distributions and packages of the Make and Play edition can freely distribute the data assets, so long as they compile with the makeandplay define and do not distribute the original levels.| Must compile with the makeandplay define set, cannot distribute the original levels. | |
| VVVVVV: Make and Play Edition |[Terry Cavanagh](http://distractionware.com/)|The free and official version of VVVVVV that includes player levels, and the tools to create your own levels, but does not include the original levels from the game.| Must compile with the makeandplay define set, cannot distribute the original levels. | [download](https://thelettervsixtim.es/makeandplay/) |
| Ved | [Dav999 and InfoTeddy](https://gitgud.io/Dav999/ved/-/graphs/master) | An external editor for VVVVVV levels. | No conditions. | [download](https://tolp.nl/ved/), [gitlab](https://gitgud.io/Dav999/ved) |
| Ved | [Dav999 and InfoTeddy](https://github.com/Daaaav/Ved/graphs/contributors) | An external editor for VVVVVV levels. | No conditions. | [download](https://tolp.nl/ved/), [github repo](https://github.com/Daaaav/Ved) |
| VVVVVV: Community Edition | https://github.com/v6cord/VVVVVV-CE/graphs/contributors | Community fork of VVVVVV focused on expanding the capabilities of player levels. | Must compile with the makeandplay define set, cannot distribute the original levels. | [github repo](https://github.com/v6cord/VVVVVV-CE) |
| VVVVVVwasm|kotborealis|Web Assembly port of VVVVVV| Must compile with the makeandplay define set, cannot distribute the original levels. | [github repo](https://github.com/kotborealis/VVVVVVwasm) |
| cursed_vvvvvv.exe | [MustardBucket](https://twitter.com/mustard_bucket/) | Modified version of VVVVVV where instead of flipping gravity you jump normally, can jump multiple times, and wall jump. | Make it impossible to revert to ordinary flipping behaviour. | [download](https://mustardbucket.itch.io/cursed-vvvvvv?secret=O0KvS02wD473pXBF9avreZsww), [twitter gif](https://twitter.com/mustard_bucket/status/1216272971779670016) |
| Haiku Port | [Julius C. Enriquez](https://github.com/win8linux) | Port for the Haiku operating system. | Display the following text in the Haiku package to make it clear that this is an exception: "VVVVVV is a commercial game! The author has given special permission to make this Haiku version available for free. If you enjoy the game, please consider purchasing a copy at [thelettervsixtim.es](http://thelettervsixtim.es)." | [haiku recipe](https://github.com/haikuports/haikuports/tree/master/games-arcade/vvvvvv), [haiku data.zip recipe](https://github.com/haikuports/haikuports/tree/master/games-arcade/vvvvvv_data) |
| Dreamcast Port | [Gustavo Aranda](https://github.com/gusarba/) | Port for the Sega Dreamcast. | Permission is given to distribute a ready-to-use CD image file for the Sega Dreamcast containing the data.zip assets for non commercial use only. | [github repo](https://github.com/gusarba/VVVVVVDC)|
| XBox One/UWP Port | [tunip3](https://github.com/tunip3) | Port for XBOX ONE (DURANGO) via UWP. | Permission is given to distribute a pre-compiled package (containing the data.zip assets) for people to run on development mode xboxes, for non commercial use only. | [github repo](https://github.com/tunip3/DURANGO-V6)|
In addition, the following exceptions apply for any projects that match these descriptions:
| Project | Description | Conditions |
|---|---|---|
| Any distribution of the VVVVVV: Make and Play edition | All distributions and packages of the Make and Play edition can freely distribute the data assets, so long as they compile with the makeandplay define and do not distribute the original levels.| Must compile with the makeandplay define set, cannot distribute the original levels. |
In general, if you're interested in creating something that falls outside the license terms, [get in touch with Terry!](http://distractionware.com/email/)
| armhf Port | [johnnyonFlame](https://github.com/johnnyonFlame/) | Armhf port for Raspberry PI and other SBC devices| Permission is for non commercial use only. Display the following text in the readme to make it clear that this is an exception: "VVVVVV is a commercial game! The author has given special permission to make this port available for free. If you enjoy the game, please consider purchasing a copy at [thelettervsixtim.es](http://thelettervsixtim.es)."| [github release](https://github.com/JohnnyonFlame/VVVVVV/releases/tag/v2.4-r1) |
| PortMaster distributions of the game for Linux Handheld devices | [portmaster](https://portmaster.games/) | A port manager GUI for Linux handheld devices | Permission is for non commercial use only. Display the following text in the readme to make it clear that this is an exception: "VVVVVV is a commercial game! The author has given special permission to make this port available for free. If you enjoy the game, please consider purchasing a copy at [thelettervsixtim.es](http://thelettervsixtim.es)."| [website](https://portmaster.games/detail.html?name=vvvvvv) |
| Wii Port | [Alberto Mardegan](https://github.com/mardy/) | Port for the Nintendo Wii. | Permission is given to distribute a ready-to-use build for the Nintendo Wii containing the data.zip assets for non commercial use only. | [github repo](https://github.com/mardy/VVVVVV/tree/wii) |
| Recalbox Port | [digitalLumberjack](https://gitlab.com/recalbox/recalbox) | Port for Recalbox project. | Display the following text in the readme to make it clear that this is an exception: "VVVVVV is a commercial game! The author has given special permission to make this port available for free. If you enjoy the game, please consider purchasing a copy at [thelettervsixtim.es](http://thelettervsixtim.es)." | [website](https://recalbox.com/) |
This is the source code to VVVVVV, version 2.0+. For more context about this release, see the [announcement](http://distractionware.com/blog/2020/01/vvvvvv-is-now-open-source/) on Terry's blog!

License
-------
VVVVVV's source code is made available under a custom license. See [LICENSE.md](LICENSE.md) for more details.
This is the source code to VVVVVV, the 2010 indie game by [Terry Cavanagh](http://distractionware.com/), with music by [Magnus Pålsson](http://souleye.madtracker.net/). You can read the [announcement](http://distractionware.com/blog/2020/01/vvvvvv-is-now-open-source/) of the source code release on Terry's blog!
In general, if you're interested in creating something that falls outside the license terms, get in touch with Terry and we'll talk about it!
The source code for the desktop version is in [this folder](desktop_version).
Authors
VVVVVV is still commercially available at [thelettervsixtim.es](https://thelettervsixtim.es/) if you'd like to support it, but you are completely free to compile the game for your own personal use. If you're interested in distributing a compiled version of the game, see [LICENSE.md](LICENSE.md) for more information.
Discussion about VVVVVV updates mainly happens on the "unofficial" [VVVVVV discord](https://discord.gg/Zf7Nzea), in the `vvvvvv-code` channel.
Credits
-------
- Created by [Terry Cavanagh](http://distractionware.com/)
- Room Names by [Bennett Foddy](http://www.foddy.net)
- Music by [Magnus Pålsson](http://souleye.madtracker.net/)
- Metal Soundtrack by [FamilyJules](http://familyjules7x.com/)
- Music by [Magnus Pålsson](https://magnuspalsson.com/)
- Metal Soundtrack by [FamilyJules](https://link.space/@familyjules)
- 2.0 Update (C++ Port) by [Simon Roth](http://www.machinestudios.co.uk)
- 2.2 Update (SDL2/PhysicsFS/Steamworks port) by [Ethan Lee](http://www.flibitijibibo.com/)
- Additional coding by [Misa Kai](https://infoteddy.info/)
- Beta Testing by Sam Kaplan and Pauli Kohberger
- Ending Picture by Pauli Kohberger
Versions
------------
There are two versions of the VVVVVV source code available - the [desktop version](https://github.com/TerryCavanagh/VVVVVV/tree/master/desktop_version) (based on the C++ port, and currently live on Steam), and the [mobile version](https://github.com/TerryCavanagh/VVVVVV/tree/master/mobile_version) (based on a fork of the original flash source code, and currently live on iOS and Android).
- Localisations by [our localisation teams](desktop_version/TRANSLATORS.txt)
- With additional contributions by [many others here on github](desktop_version/CONTRIBUTORS.txt) <3
option(ENABLE_WERROR "Treat compilation warnings as errors" OFF)
option(BUNDLE_DEPENDENCIES "Use bundled TinyXML-2, PhysicsFS, and UTF8-CPP (if disabled, TinyXML-2 and PhysicsFS will be dynamically linked, LodePNG and UTF8-CPP will still be statically linked)" ON)
set(CUSTOM_LEVEL_SUPPORT ENABLED CACHE STRING "Optionally disable playing and/or editing of custom levels")
option(BUNDLE_DEPENDENCIES "Use bundled TinyXML-2, PhysicsFS, and FAudio (if disabled, TinyXML-2, PhysicsFS, and FAudio will be dynamically linked; LodePNG, C-HashMap and SheenBidi will still be statically linked)" ON)
option(STEAM "Use the Steam API" OFF)
option(GOG "Use the GOG API" OFF)
@ -24,18 +21,11 @@ if(OFFICIAL_BUILD AND NOT MAKEANDPLAY)
set(GOG ON)
endif()
if(${CMAKE_VERSION} VERSION_LESS "3.1.3")
message(WARNING "Your CMake version is too old; set -std=c90 -std=c++98 yourself!")
else()
set(CMAKE_C_STANDARD 90)
set(CMAKE_C_EXTENSIONS OFF)
option(REMOVE_ABSOLUTE_PATHS "If supported by the compiler, replace all absolute paths to source directories compiled into the binary (if any) with relative paths" ON)
If you need a font (like a TTF) converted into the format that the game can read, for now you might want to ask Dav, who has tools for it.
=== F O N T F O R M A T ===
Fonts consist of two files: a .png and a .fontmeta. The .png contains all the "images" for all glyphs, and the .fontmeta is an XML document containing all information about which characters are in the file and other metadata.
For example, a font for Japanese might be called font_ja.png and font_ja.fontmeta.
The fontmeta file looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<font_metadata>
<display_name>日本語</display_name>
<width>12</width>
<height>12</height>
<white_teeth>1</white_teeth>
<chars>
<range start="0x20" end="0x7F"/>
<range start="0xA0" end="0x17F"/>
<range start="0x18F" end="0x18F"/>
<range start="0x218" end="0x21B"/>
<range start="0x259" end="0x25A"/>
<!-- ... -->
</chars>
<special>
<range start="0x00" end="0x1F" advance="6"/>
<range start="0xEB00" end="0xEBFF" color="1"/>
</special>
<fallback>buttons_12x12</fallback>
</font_metadata>
* type: not specified for normal fonts. <type>buttons</type> is used in button glyph fonts.
* display_name: the name of the language the font is specifically meant for - in the language itself. Users will see this in the level editor when choosing a font to use. If this font is used equally by multiple translations, this could be set to a combination like "繁體中文/한국어". (If you are creating a custom player level: don't worry about this)
* width/height: the width and height of each glyph in the font. Every character will always be drawn as a rectangle of this size. VVVVVV is rated to support fonts up to 12 pixels high - anything higher may cause text overlapping or not fitting in place.
* white_teeth: indicates that all characters in the font are white, so the game itself doesn't have to remove all color from the image and make all pixels white like it would in old versions of the game. If this is not set to 1, this font cannot have colored (button) glyphs, and the game has to specifically process the font every time it is loaded, so 1 is highly recommended.
* chars: defines which characters are in the image. Starting at the top left of the image, each character is simply a rectangle of equal size (defined in <width> and <height>) from left to right, top to bottom. In the example given above, the image first has Unicode every character from U+0020 up to and including U+007F, then every character from U+00A0 to U+017F, and so on. To include a single character, simply use a range with equal start and end attributes equal.
* special: defines special attributes that will be applied to a range of characters. One or more of the following attributes can be used:
- color: set to 1 if these glyphs should be drawn with its original colors (for button glyphs, or even emoji...)
- advance: instead of <width>, the cursor (for drawing the next character) should be advanced this amount of pixels to the right. This controls the width of a character, but it does not affect how characters are arranged in the image, and the full glyph will still be drawn. While this means the font system has support for variable-width fonts, it's recommended to not use this option. There are some problems arising from using a variable-width font (especially in text boxes), so please consider copying the font's fullwidth forms (U+FF01-U+FF5E) to ASCII U+0021-U+007E instead. One may argue that a monospaced font also fits more with the game's style.
* fallback: specifies the button glyphs font to use. Make sure to choose one that fits fully within your [width]x[height] rectangle, so for an 8x12 font, choose buttons_8x8, not buttons_12x12.
This file will explain what you need to know when maintaining translations of VVVVVV (like adding new strings to the game and syncing them across languages).
For making new translations of the game, read README-translators.txt instead.
=== A D D I N G N E W S T R I N G S ===
If you want to add some new text to the game, all you generally need to do to make it translatable is wrap loc::gettext() around your raw strings in the code (you may need to add an #include "Localization.h"), and add the same strings to the English language file. The new strings can be automatically synced from English to all other language files using the translator menu.
For example, "Game paused" can be made translatable by changing it to loc::gettext("Game paused"). Its entry in the English language file could look like this:
The max value indicates how many characters of space there is for the text, and is further described below. It looks like "40" for single-line text, and "38*5" for multi-line text. The max value may not be applicable or may be hard to define, so this attribute can be completely left out. For example, when it's a diagonally-placed menu option, or because the limit depends on the lengths of other strings (like Low/Medium/High in the joystick menu), or a string looks progressively worse the longer it gets. As a general rule: if defining a hard limit would be misleading, then it can be exempt from having a limit.
=== E D I T I N G E X I S T I N G S T R I N G S ===
Sometimes you need to make a partial change to text that has already been translated.
For example: you need to change the string "Press ENTER to stop" to "Press {button} to stop" (because you made that hotkey configurable).
Please do *not* simply find-and-replace the text in the language files, nor remove the old string when you add the new one. (Whether you keep the translations or not.)
Instead, duplicate the string in the English language file, putting your version underneath the old version, and mark the old version with the explanation "***OUTDATED***".
For example:
<string english="Press ENTER to stop" translation="" explanation="stop super gravitron"/>
↓ ↓ ↓
<string english="Press ENTER to stop" translation="" explanation="***OUTDATED***"/>
<string english="Press {button} to stop" translation="" explanation="stop super gravitron"/>
The game won't be using the outdated string anymore - it's only still in the language files to carry the existing translations for reference. Once a translator updates the language files, they can reuse parts of the old translation for the new version of the string.
font::print_wrap(PR_CEN, -1, 50, "Hello world, this will wordwrap to the screen width", 255, 255, 255);
A not-technically-exhaustive list of all flags (which are defined in Font.h):
- PR_2X/PR_3X/.../PR_8X Print at larger scale (PR_1X is default)
- PR_FONT_INTERFACE [DEFAULT] Use interface (VVVVVV language) font
- PR_FONT_LEVEL Use level-specific font (room names, cutscenes, etc)
- PR_FONT_8X8 Use 8x8 font no matter what
- PR_BRIGHTNESS(value) Use this brightness 0-255 (this value is mixed with
r, g and b for an alpha effect, and accounts for
colored glyphs correctly)
- PR_BOR Draw a black border around the text
- PR_LEFT [DEFAULT] Left-align text/place at X coordinate
- PR_CEN Center-align text relative to X (X is center)
or to screen if X == -1
- PR_RIGHT Right-align text to X
(X is now the right border, not left border)
- PR_CJK_CEN [DEFAULT] Large fonts (Chinese/Japanese/Korean) should
stick out on top and bottom compared to 8x8 font
- PR_CJK_LOW Larger fonts should stick out fully on the bottom
(draw at Y)
- PR_CJK_HIGH Larger fonts should stick out fully on the top
- PR_RTL_XFLIP In RTL languages, mirror the X axis, so left is 320
and right is 0, and invert the meaning of PR_LEFT and
PR_RIGHT
=== S T R I N G F O R M A T T I N G ===
Instead of sprintf-family functions, it is preferred to use VVVVVV's own formatting system, VFormat.
Strings sometimes have placeholders, which look like {name} or {name|flags}. For example, "{n_trinkets} of {max_trinkets}".
Placeholders can also have "flags" that modify their behavior. These can be added or removed in the translation as needed. Flags are separated by | (pipe).
For more info, see the documentation at the top of VFormat.h.
Full example:
char buffer[100];
vformat_buf(buffer, sizeof(buffer),
"{crewmate} got {number} out of {total} trinkets in {m}:{s|digits=2}.{ms|digits=3}",
=> "Vermilion got 2 out of 20 trinkets in 2:03.001"
=== T R A N S L A T O R M E N U ===
The translator menu has options for both translators and maintainers - it allows testing menus, translating room names within the game, syncing all language files with the English template files, getting statistics on translation progress, and more.
VVVVVV will show a "translator" menu in the main menu if either:
- The "lang" folder is NOT next to data.zip, and the game is running somewhere within a "desktop_version" folder, and desktop_version/lang IS found. This normally happens when compiling the game from source;
- The command line argument (or launch option) "-translator" is passed.
- ALWAYS_SHOW_TRANSLATOR_MENU is defined during compilation (see top of Localization.h)
To add new strings, add them to only the English strings.xml or strings_plural.xml, and use the option to sync all languages from the translator menu. This will copy the new strings to all translated language files.
The language file sync option has differing support for the language files. As indicated in the menu itself, it handles each file as follows:
[Full syncing EN→All]
For these files, the English version of the file is fully copied and overwrites every language's version, while all existing translations and customizations are inserted for every language. This means newly added strings are copied to every language, and removed strings are simultaneously removed from every language, bringing them fully up-to-date.
- meta.xml
- strings.xml
- strings_plural.xml
- cutscenes.xml
- roomnames.xml
- roomnames_special.xml
[Syncing not supported]
These files are untouched by the syncing feature.
- numbers.xml
=== F I L E S ===
* meta.xml: This file contains some general information about a translation.
* strings.xml: This file contains general strings for the interface and some parts of the game.
* strings_plural.xml: Similar usage to strings.xml, but for strings that need different plural forms ("1 trinket", "2 trinkets")
* numbers.xml: This file contains all numbers from 0 to 100 written out (Zero, One, etc). This file also allows you to define the plural forms used in strings_plural.xml.
* cutscenes.xml: This file contains nearly all the cutscenes that appear in the main game.
* roomnames.xml: This file contains nearly all the room names for the main game.
* roomnames_special.xml: This file contains some special cases for roomnames, some names for rooms that usually aren't displayed as regular (like The Ship), and some general area names.
This file will explain everything you need to know when making translations of VVVVVV.
WARNING TO VOLUNTEERS: VVVVVV translation is not a community effort where anyone can submit translations! It's *possible* that, on a case-by-case basis, someone can volunteer to become an official translator. But you would need to get approval from Terry first (or you can express interest in there being an official translation into your language, it may already be planned!)
Likewise, you're welcome to report issues in existing translations (or to submit PRs to fix these issues), but it's not a good idea to rewrite significant parts of a translation, and then contribute it without warning. If you think there are errors or things that could be improved, please give an explanation as to why. (We may decide to discuss it with the official translator.)
It *is* possible to make a fan translation for fun, for sharing with others, etc. But it probably won't be distributed with the game officially.
=== A D D I N G A N E W L A N G U A G E ===
The English language files are basically templates for other languages (all the translations are empty).
To create a new language, simply copy the `en` folder, and start by filling out meta.xml (further explained below).
Alternatively, you can create an empty language folder, and then use the in-game sync tool (translator > maintenance > sync language files) to populate it.
=== E X C E L ===
The game uses XML files for storing the translations. If you prefer, there is an .xlsm file which can be used as an editor. This can load in all the XML files, and then save changes back as XML.
If you're an official translator, you should have received a version of this spreadsheet. If not, a blank version can be found here: https://github.com/Daaaav/TranslationEditor
=== T R A N S L A T O R M E N U ===
The translator menu has options for both translators and maintainers - it allows testing menus, translating room names within the game, syncing all language files with the English template files, getting statistics on translation progress, and more. The translator menu is hidden from players in regular versions of the game.
When the translator menu is unlocked, you can also press F8 anywhere in the game to reload the current language files. So you can save translations and immediately preview them (except for menu buttons and the current cutscene dialogue, which can't be reloaded on the fly). You will hear a coin sound when the language files have been reloaded via F8.
=== L O W E R C A S E A N D U P P E R C A S E ===
If lowercase and uppercase does not exist in your language (Chinese, Japanese and Korean for example), you can set toupper to 0 in meta.xml, and ignore any directions about using lowercase or uppercase.
VVVVVV's menu system has the style of using lowercase for unselected options and uppercase for selected options, for example:
play
levels
[ OPTIONS ]
translator
credits
quit
The menu options are stored as their full-lowercase version, and they're normally commented as "menu option" in the translation files. A built-in function (toupper in Localization.cpp) automatically converts the lowercase text to uppercase when needed. This function has support for a good number of accented characters, Cyrillic and Greek, but more could be added if needed. It also accounts for special cases in Turkish and Irish.
Turkish: The uppercase of i is İ, for example, "dil" becomes "DİL" and not "DIL". To enable this, set toupper_i_dot to 1 in meta.xml.
Irish: Specific letters may be kept in lowercase when making a string full-caps. For example, "mac tíre na hainnise" should be "MAC TÍRE NA hAINNISE" instead of "MAC TÍRE NA HAINNISE". If enabled, you can use the ~ character before the letter which should be forced in lowercase: "mac tíre na ~hainnise". This ~ character only has an effect in strings which are subject to automatic uppercasing (otherwise it'll be visible as å). This can be enabled by setting toupper_lower_escape_char to 1 in meta.xml.
=== W O R D W R A P P I N G A N D L E N G T H L I M I T S ===
For most languages, VVVVVV can automatically wordwrap based on spaces. This may not work for some languages (like Chinese, Japanese and Korean), so instead, newlines can be inserted manually (see below) and automatic wordwrapping can be disabled in meta.xml.
VVVVVV's resolution is 320x240, and the default font is 8x8, which means there is a 40x30 character grid (although we don't adhere to this grid for the UI, but it gives a good indication). Naturally, if the font has a different size like 12x12, less characters will fit on the screen too.
Strings are usually annotated with their limits (for example, max="38*3"). This can be formatted like one of the following:
(A) 33
(B) 33*3
(A) if it's a single number (for example "33"): the hard maximum number of characters that are known to fit. Being exactly on the limit may not look good, so try to go at least a character under it if possible.
(B) if X*Y (for example 33*3): the text should fit within an area of X characters wide and Y lines high. The text is automatically word-wrapped to fit (unless disabled in meta.xml). If automatic word-wrapping is disabled, you need to manually insert newlines with |, or possibly as a literal newline.
If your language uses a font with a different size than 8x8, there will be two limits given: `max`, which is the original limit based on the 8x8 font, and `max_local`, which is adapted to the size of your font. To get this notation, use the maintenance option to sync language files from within VVVVVV. Ensure the correct font is set in meta.xml first.
The translator menu has an option ("limits check") to automatically find strings that break the given limits. There may be a few cases where this detection isn't perfect, but it should be a helpful quality assurance tool.
The maximum lengths are not always given. Notoriously, menu option buttons are placed diagonally, thus they have maximums that are hard to look up. Even more so, making an option differ too much in length from the other options might make it look out of place. Best thing to do there is probably just translate as usual and then test all menus via the "menu test" option in the translator menu. However, menus do automatically reposition based on the text length, so worst-case scenario, if an option is 36 characters long, all options are displayed right underneath each other.
=== F O N T S ===
The game uses an 8x8 pixel font by default (font.png and font.fontmeta in the "fonts" folder). If your language can be represented in 8x8 characters, it is preferable to use this font, or for this font to be extended.
The fonts directory also has a README.txt file that explains how the font format works.
=== N U M B E R S A N D P L U R A L F O R M S ===
In certain places, VVVVVV (perhaps unconventionally) writes out numbers as full words. For example:
- One out of Fifteen
- Two crewmates remaining
- Two remaining
These words can be found in numbers.xml. The numbers Zero through Twenty will be the most commonly seen. It's always possible for numbers up to One Hundred to be seen though (players can put up to 100 trinkets and crewmates in a custom level).
Your language may not allow the same word to be used for the same number in different scenarios. For example, in Polish, "twenty out of twenty" may be "dwadzieścia z dwudziestu". Right now, you have two sets of wordy numbers to choose from, `translation` and `translation2`, but this will likely change to a more customizable system in the future. You can choose when these "wordy" numbers are used and when numeric forms (20 out of 20) are used (see "STRING FORMATTING" below). It's also possible to leave the translations for all the numbers empty. In that case, numeric forms will always be used.
In English, using Title Case is appropriate, but in most other languages, it probably isn't. Therefore, you may want to translate all numbers in lowercase, when it's more appropriate to use "twenty out of twenty" than "Twenty out of Twenty". You can then apply auto-uppercasing to any placeholder you choose (see "STRING FORMATTING" below), making it possible to display "Twenty out of twenty".
As for plural forms: English and some other languages have a singular (1 crewmate) and a plural (2 crewmates). Some languages may have different rules (like for 0, or numbers that end in 2, 3 and 4). VVVVVV can accommodate these rules and allows you to translate certain strings (strings_plural.xml) in different ways depending on the number. The different forms can be defined by changing the "form" attribute on each number in numbers.xml. For English, form "1" is used for singular, and form "0" is used for plural. You can set up any amount of plural forms you will need.
Numbers that identify the forms do not need to be sequential, you may use any number between 0 and 254 to identify the different forms. So instead of using forms 0, 1, 2 and 3, you could also name them 1, 2, 5 and 7.
Suppose you need a different form for the number 1, the numbers 2-4, and all other numbers. You could use "form 1" for the number 1, "form 2" for 2-4, and "form 0" for all other numbers:
<numbers>
<number value="0" form="0" ... />
<number value="1" form="1" ... />
<number value="2" form="2" ... />
<number value="3" form="2" ... />
<number value="4" form="2" ... />
<number value="5" form="0" ... />
<number value="6" form="0" ... />
...
When translating the plural strings, you can add translations for every unique form. For example:
Plural forms can appear both for wordy numbers ("you saved one crewmate") as well as numbery numbers ("you died 136 times in this room"), so we need the plural forms to go further than 100.
For the numbers 100 and higher: as far as I can find (with information about plural rules across 160 languages) - the plural forms always repeat themselves every 100 numbers. So numbers 100-199 always have the same forms as 200-299, 300-399, and so on. However, 100-119 (200-219, etc) don't always work the same as 0-19 do (in English for example, it's not "101 trinket" despite ending in 01). Therefore, forms for 100-119 can also be filled in. The system will simply copy 20-99 for 120-199, and that should be enough to cover all numbers from 0 to infinity. Technically the system supports providing forms until 199, but it should never be necessary to go higher than 119, so they're not in the language files by default.
Numbers higher than 100 cannot have a written out translation ("one hundred and one" does not exist).
=== S T R I N G F O R M A T T I N G ===
Strings sometimes have placeholders, which look like {name} or {name|flags}. For example, "{n_trinkets} of {max_trinkets}".
Placeholders can also have "flags" that modify their behavior. These can be added or removed in the translation as needed. Flags are separated by | (pipe).
For example, "{n_trinkets|wordy}" makes the number of trinkets display as a "wordy" number (twenty instead of 20) (See "NUMBERS AND PLURAL FORMS"). "{n_trinkets|wordy|upper}" makes that word start with a capital letter (Twenty instead of twenty). So for example, "{n_trinkets|wordy|upper} of {max_trinkets|wordy}" may be displayed as "Twenty out of twenty" - assuming numbers.xml is translated all-lowercase.
The valid flags are:
- wordy [ints only] use number words (Twenty) of the first set (translation), instead of digits (20)
- wordy2 [ints only] use number words of the second set (translation2), instead of digits
- digits=n [ints only] force minimum n digits, like n=5 --> 00031
- spaces [only if using digits=n] use leading spaces instead of 0s
- upper uppercase the first character with loc::toupper_ch
=== S T O R Y A N D C H A R A C T E R I N F O R M A T I O N ===
This is a brief story and character overview for reference. Any further questions can be directed to Terry: https://distractionware.com/email/
== The Crewmates ==
VVVVVV is about a crew of curious and super-intelligent aliens exploring the universe. There are six crewmates onboard the ship, in six different colours. They are:
* Viridian (Cyan, the player character)
* Verdigris (Green)
* Vitellary (Yellow)
* Vermilion (Red)
* Victoria (Blue)
* Violet (Purple)
All six characters have names that start with V. In addition, each crewmate's name is an obscure word that suggests their colour - for example, Verdigris is the name of the green pigment that forms on copper when it oxidises. This might be hard to translate! (So, potentially it just makes sense not to translate the names at all, unless you have a good idea about how to do it - for example, if you can pull off the same colour/name beginning with V trick in your language.)
Each crewmate has the following "rank", i.e. their job on the ship. These are all basically just Star Trek inspired roles:
* "Captain" Viridian (Captain as in leader)
* "Chief" Verdigris (Chief as in Lead Engineer)
* "Professor" Vitellary (Kind of the senior scientist on board)
* "Officer" Vermilion (Officer as in Away-Officer, the one who usually goes out exploring)
* "Doctor" Victoria (Doctor in the scientific sense)
* "Doctor" Violet (Doctor in the medical sense)
Verdigris, Vitellary and Vermilion are male. Victoria and Violet are female. Viridian is deliberately unspecified - when translating cutscenes, if at all possible, try to avoid specifying their gender, even if that leads to some otherwise awkward phrasing or requires the use of some cutting edge grammar tricks in your language.
== Personalities ==
If it helps with tone: the running joke in VVVVVV's writing is that all six characters are hyper intelligent prodigies, who will nevertheless speak to each other as if they're small children. E.g. Viridian "we were just playing with it!", Vermilion "I'm helping!", Verdigris "I'm an engineer!", etc. More specifically:
* Viridian is a classic hero - unphased by danger, never worried, always completely certain that they'll be able to fix everything.
* Verdigris is a romantic - he has a huge (reciprocated!) crush on Violet, which he does a terrible job of keeping secret.
* Vitellary is an academic - he loves science, and has a dry and long winded science thing to tell you in almost every cutscene.
* Vermilion is bold and adventurous - after you rescue him, you'll find him exploring different parts of the world on his own quest to find the rest of the crew (he's not much help, though).
* Victoria is a worrier - she's quick to feelings of despair. Victoria's sprite is almost always sad!
* Violet is a caretaker - she's the ship's Doctor, and most of her cutscenes are status updates about the other crewmates.
== Dimension VVVVVV ==
The world you're exploring is filled with terminals, with text logs from the previous inhabitants, who we never see. We don't know much about them.
The ship you're all on is called the "D.S.S. Souleye", which is a minor easter egg. D.S.S. just stands for "Dimensional Space Ship" - a craft that warps between different dimensions. Souleye is the pseudonym for Magnus Pålsson, the game's composer.
=== S P R I T E S T R A N S L A T I O N ===
There are several enemies in the game which contain words: STOP, YES, OBEY, LIES and TRUTH. These, as well as the C on checkpoints, can be translated.
This may be a bit tricky - the sizes of the translated graphics should be as close to the original English as possible, which means that even though letters can be compressed a bit, they will quickly be too long. For example, if you'd like to translate LIES with 5 letters, it really doesn't help if one of those 5 letters is not an I (the most slim letter).
Fortunately, the translation does not have to be literal, as the words themselves are only referenced in room names at most, and the exact meanings aren't that important, only the spirit of them is. OBEY is a They Live reference, so there are a lot of other signs you could use. Some inspiration:
* STOP: this is on a stop sign, so may not need to be translated
* OBEY: maybe something like FOLLOW, or one of the other words in "They Live" (WORK, BUY, CONSUME, CONFORM, ...)
* LIES: maybe singular LIE, maybe some form of NONSENSE (as in BS but non-offensive), FALSE, FAKE, BLAH, FABLE
* TRUTH: maybe FACTS
== Implementation of translated sprites ==
If you'd like to pixel translated sprites yourself: Take sprites.png and flipsprites.png from the graphics folder in data.zip. These spritesheets are a 32x32 grid - for example, you cannot extend OBEY upwards or to the left.
The translated file does not have to contain any untranslated sprites - these can simply be made transparent, for optimization. So: the files should be transparent sheets with some translations in the middle.
The translated versions of sprites.png and flipsprites.png can be placed in a "graphics" subfolder of your language folder.
Then, put a file next to them called "spritesmask.xml", with the following contents:
<?xml version="1.0" encoding="UTF-8"?>
<spritesmask sprite_w="32" sprite_h="32">
<sprite x="8" y="1" w="2"/> <!-- Checkpoints -->
<sprite x="4" y="2" w="4"/> <!-- STOP -->
<sprite x="4" y="3" w="4"/> <!-- YES -->
<sprite x="3" y="4"/> <!-- OBEY -->
<sprite x="2" y="5" w="2"/> <!-- LIES receiver and LIES -->
<sprite x="4" y="5" w="2"/> <!-- TRUTH -->
</spritesmask>
This file defines which sprites in the translated files should overwrite the original ones. Remove any lines for sprites you did not translate. After that, your translated sprites should work!
For completeness: sprite_w and sprite_h on the <spritesmask> tag define the size of each unit in the attributes of <sprite>. The possible attributes of <sprite> are:
* x, y: the position of the sprite to copy (in blocks of sprite_w by sprite_h)
* w, h: the width and height of the sprite to copy (default 1 for both)
* dx, dy: the destination position of the sprite (default x,y; so the same position as copied from)
=== F I L E S ===
== meta.xml ==
This file contains some general information about this translation. It contains the following attributes:
* active: If 0, this language will not be shown in the languages menu
* nativename: The name of the language in itself, fully in lowercase (so not "spanish" or "Español", but "español"). A language name can be at most 16 characters wide (in the 8x8 font)
* credit: You can fill in credit here that will appear on the language screen, like "Spanish translation by X". May be in your language. Max 38*2 @8x8
* action_hint: This is displayed at the bottom of the language screen when your language is highlighted, to show that Space/Z/V sets the selected option as the language. Max 40 @8x8
* gamepad_hint: The same as action_hint, but now a gamepad button will be filled into {button}, such as (A), (X), etc
* autowordwrap: Whether automatic wordwrapping is enabled. Can be disabled for CJK (in which case newlines have to be inserted manually in text)
* toupper: Whether to enable automatic uppercasing of menu options (unselected, SELECTED). May be disabled for languages such as CJK that don't have lowercase and uppercase.
* toupper_i_dot: When automatically uppercasing, map i to İ, as in Turkish.
* toupper_lower_escape_char: When automatically uppercasing, allow ~ to be used to stop the next letter from being uppercased, for Irish.
* rtl: This should be enabled for languages that are written right-to-left. It will horizontally flip many parts of the interface and allows text to be right-aligned in places like textboxes.
* menu_select: The indication that a certain menu option or button is selected, in addition to the automatic uppercasing if "toupper" is enabled. For example, "[ {label} ]" looks like "[ SELECTED ]"
* menu_select_tight: Similar to menu_select, except used in cases where space is a bit more limited (like the map screen). "[{label}]" looks like "[SELECTED]"
* font: The filename of the font to use. The 8x8 font is called "font", the Japanese font is "font_ja", et cetera.
== strings.xml ==
This file contains general strings for the interface and some parts of the game. In the XML, the tag for one string looks like this:
If the translation is left blank, the English text will be used as a fallback. Translations should NOT be left blank on purpose if the text is the same however; the string will be counted as untranslated and it'll be harder to keep track of what's new. Always just copy-paste the English string into the translation in that case.
The following attributes may be found for each string:
* english: the English text.
* translation: the translation.
* case: a number (1, 2, etc) for separate strings that are identical in English but may need different translations.
* explanation: an explanation about the context, location and possibly the formatting.
* max: length restrictions, described above in "WORDWRAPPING AND LENGTH LIMITS"
== strings_plural.xml ==
See "NUMBERS AND PLURAL FORMS" above.
You can define the plural forms in numbers.xml.
Then, simply add translations for each form you set up in numbers.xml. For example:
<translation form="0" translation="Shows up for all numbers with form=0"/>
<translation form="1" translation="Shows up for all numbers with form=1"/>
<translation form="2" translation="Shows up for all numbers with form=2"/>
The "wordy" flag indicates a word will be filled in (like twelve), otherwise a number (12). As described above in "STRING FORMATTING", you can change this as needed in your translations.
The `var` attribute indicates which placeholder will be filled in, the `expect` attribute indicates how high the values are that you may expect to be filled in. For example, expect="20" means any value above 20 will probably not be used in this string. This is mainly needed so that the limits check knows not to worry about a number like "seventy seven" making the string too long, but it may also be a useful context clue.
== numbers.xml ==
This file contains all numbers from 0 to 100 written out (Zero, One, etc).
This will be filled in strings like:
- One out of Fifteen
- Two crewmates remaining
- Two remaining
If this can't work for your language, or wordy numbers are really unfitting, you can leave all of these empty, in which case numbers will be used (20 out of 20).
You may want to do it all-lowercase in order to not get English-style title casing. "Twenty out of Twenty" may be grammatically incorrect in MANY languages, and "twenty out of twenty" would be better. Translating the numbers all-lowercase allows you to apply context-specific uppercasing, like "Twenty out of twenty" (see "STRING FORMATTING" above)
This file also allows you to define the plural forms used in strings_plural.xml.
For more information, see "NUMBERS AND PLURAL FORMS" above.
== cutscenes.xml ==
This file contains nearly all the cutscenes that appear in the main game. Each line has a "speaker" attribute, which is not used by the game - it's just for translators to know who says what and to establish context.
The dialogues are automatically text-wrapped, except if automatic wrapping is disabled in meta.xml. In that case, the maximum line length is 36 8x8 characters (288 pixels) or 24 12x12 characters.
In the few cases where the same text appears multiple times in a cutscene, these have the attribute "case" added to them (for example case="1", case="2", etc), so they can be translated separately if needed. (These match up with textcase(x) commands in the scripts themselves)
You may find some additional formatting attributes on each <dialogue> tag. These are used to make spacing and formatting in translations consistent with the original English text (for example, centered text, padding on both sides, etc). You can change any of these if you need, and you can also add them yourself to ANY dialogue tag.
* tt: teletype. if "1", disable automatic word wrapping, even if autowordwrap is enabled in meta.xml. You will have to add newlines manually for this textbox, either with hard enters, or with |
* wraplimit: change the maximum width of the text before it wordwraps, in pixels. Only if tt is not enabled. Example:
If autowordwrap is disabled in meta.xml, this also doesn't work, but it does give you an advisory maximum text width.
The default is 288 (36*8 or 24*12).
* centertext: center the text (but keep it aligned to the grid), for example:
[You have rescued] --[centertext="1"]--> [You have rescued]
[a crewmember!] [ a crewmember! ]
* pad: pad each line of text with a number of spaces (0 by default), for example:
[You have rescued] --[pad="2"]--> [ You have rescued ]
[ a crewmember! ] [ a crewmember! ]
This will automatically make the wrap limit smaller accordingly, unless a custom wraplimit is given.
* pad_left/pad_right: same as pad, but only affects the left or right side. For example:
[You have rescued] --[ pad_left="5"]--\ [ You have rescued ]
[ a crewmember! ] --[pad_right="2"]--/ [ a crewmember! ]
* padtowidth: pad the text on both sides if it's not this many pixels wide. For example:
[-= Personal Log =-] --[padtowidth="224"]--> [ -= Personal Log =- ] (224=28*8)
== roomnames.xml ==
This file contains nearly all the room names for the main game. The limit is always 40 8x8 characters (320 pixels) or 26 12x12 characters.
It's recommended to translate the room names in-game to see why all rooms are called what they are. To do this, enable room name translation mode in translator > translator options > translate room names.
== roomnames_special.xml ==
This file contains some special cases for roomnames, some names for rooms that usually aren't displayed as regular (like The Ship), and some general area names.
One room ("Prize for the Reckless") is intentionally missing spikes in a time trial and no death mode so the player does not have to die there, and the room is called differently in both cases (for time trial "Imagine Spikes There, if You Like", and for no death mode "I Can't Believe You Got This Far").
There are also some roomnames in the game which gradually transform into others or cycle through a few minor variations.
<dialoguespeaker="purple"english="Something has gone horribly wrong with the ship's teleporter!"translation="حصل خلل رهيب في آلة تنقيل السفينة!"/>
<dialoguespeaker="purple"english="I think everyone has been teleported away randomly! They could be anywhere!"translation="أظن أنه بعثر أفراد طاقمنا إلى أماكن عشوائية! لا أستغرب أي مكان أرسلوا إليه!"/>
<dialoguespeaker="purple"english="I'm on the ship - it's damaged badly, but it's still intact!"translation="أنا قرب السفينة. صحيح أنها تضررت كثيرا، لكنها لا تزال صالحة!"/>
<dialoguespeaker="purple"english="Where are you, Captain?"translation="أين أنت يا قبطان؟"/>
<dialoguespeaker="cyan"english="I'm on some sort of space station... It seems pretty modern..."translation="أنا قرب محطة فضائية ما... تبدو لي حديثة بعض الشيء..."/>
<dialoguespeaker="purple"english="There seems to be some sort of interference in this dimension..."translation="يبدو أن في هذا البعد نوعا ما من التشويش..."/>
<dialoguespeaker="purple"english="I'm broadcasting the coordinates of the ship to you now."translation="سأرسل إليك إحداثيات السفينة."/>
<dialoguespeaker="purple"english="I can't teleport you back, but..."translation="لا يمكنني أن أجلبك بآلة التنقيل، لكن على الأقل..."/>
<dialoguespeaker="purple"english="If YOU can find a teleporter anywhere nearby, you should be able to teleport back to me!"translation="لو عثرت بنفسك على آلة تنقيل قريبة منك، يمكنك التنقل والعودة إلي!"/>
<dialoguespeaker="cyan"english="Ok! I'll try to find one!"translation="حاضر! سأحاول أن أجد أحد هذه الأجهزة!"/>
<dialoguespeaker="purple"english="Good luck, Captain!"translation="حظا موفقا، يا قبطان!"/>
<dialoguespeaker="purple"english="I'll keep trying to find the rest of the crew..."translation="أما أنا، سأواصل البحث عن بقية أفراد الطاقم..."/>
</cutscene>
<cutsceneid="trenchwarfare"explanation="player finds Trench Warfare trinket, if no trinkets found yet">
<dialoguespeaker="cyan"english="Ohh! I wonder what that is?"translation="أوووه! يا ترى ما هذا الشيء اللماع؟"/>
<dialoguespeaker="cyan"english="I probably don't really need it, but it might be nice to take it back to the ship to study..."translation="لا أظنني بحاجة إليه، لكن لا بأس في العودة به للسفينة كي نتفحصه هناك..."/>
</cutscene>
<cutsceneid="newtrenchwarfare"explanation="player finds Trench Warfare trinket, if other trinket already found">
<dialoguespeaker="cyan"english="Oh! It's another one of those shiny things!"translation="أوه! شيء آخر من نفس نوع تلك الأشياء اللماعة!"/>
<dialoguespeaker="cyan"english="I probably don't really need it, but it might be nice to take it back to the ship to study..."translation="لا أظنني بحاجة إليه، لكن لا بأس في العودة به للسفينة كي نتفحصه هناك..."/>
<dialoguespeaker="player"english="So, Doctor - have you any idea what caused the crash?"translation="أخبريني يا دكتورة - هل عندك فكرة عن سبب الحادث؟"/>
<dialoguespeaker="purple"english="There's some sort of bizarre signal here that's interfering with our equipment..."translation="توجد هنا إشارة غريبة تتداخل مع سير عمل تجهيزاتنا..."/>
<dialoguespeaker="purple"english="It caused the ship to lose its quantum position, collapsing us into this dimension!"translation="تسبب ذلك بضياع الإحداثية الكمية للسفينة، فوقع تسطيحنا إلى هذا البعد!"/>
<dialoguespeaker="purple"english="But I think we should be able to fix the ship and get out of here..."translation="مع هذا لن نعجز عن إصلاح السفينة والمغادرة..."/>
<dialoguespeaker="purple"english="... as long as we can find the rest of the crew."translation="... بشرط أن نجمع شمل بقية الطاقم."/>
<dialoguespeaker="purple"english="We really don't know anything about this place..."translation="لا نعرف عن هذا المكان شيئا..."/>
<dialoguespeaker="purple"english="Our friends could be anywhere - they could be lost, or in danger!"translation="من يدري أين أصدقاؤنا - هل هم تائهون؟ هل يتعرضون للأخطار؟"/>
<dialoguespeaker="player"english="Can they teleport back here?"translation="هل يمكنهم التنقيل والعودة إلينا؟"/>
<dialoguespeaker="purple"english="Not unless they find some way to communicate with us!"translation="كلا، طالما لم يتواصلوا معنا بطريقة ما!"/>
<dialoguespeaker="purple"english="We can't pick up their signal and they can't teleport here unless they know where the ship is..."translation="لم نلتقط إشارتهم، ولن يتنقلوا هنا إلا لو عرفوا مكان السفينة..."/>
<dialoguespeaker="player"english="So what do we do?"translation="هكذا إذن، لكن ما الحل؟"/>
<dialoguespeaker="purple"english="We need to find them! Head out into the dimension and look for anywhere they might have ended up..."translation="أن نجدهم! لو غامرت في هذا البعد الجديد وبحثت في كل مكان ربما نلقاهم..."/>
<dialoguespeaker="player"english="Ok! Where do we start?"translation="هذا مقدور عليه... طيب! من أين نبدأ؟"/>
<dialoguespeaker="purple"english="Well, I've been trying to find them with the ship's scanners!"translation="حاولت أن أجدهم براصدات سفينتنا!"/>
<dialoguespeaker="purple"english="It's not working, but I did find something..."translation="لم ينفع ذلك، لكن وجدت أمرا يستحق الذكر..."/>
<dialoguespeaker="purple"english="These points show up on our scans as having high energy patterns!"translation="تلك النقاط في أرصادنا تشير إلى أنساق طاقة فائقة!"/>
<dialoguespeaker="purple"english="There's a good chance they're teleporters - which means they're probably built near something important..."translation="من المرجح أنها آلات تنقيل - يدل هذا أن مكان بنائها قرب شيء مهم..."/>
<dialoguespeaker="purple"english="They could be a very good place to start looking."translation="قد تكون تلك النقاط أفضل بداية لبحثنا."/>
<dialoguespeaker="player"english="Ok! I'll head out and see what I can find!"translation="واضح! إذن سأخرج وأرى ماذا عساي أجد!"/>
<dialoguespeaker="purple"english="I'll be right here if you need any help!"translation="سأنتظرك هنا في حال ما احتجت أي مساعدة!"/>
</cutscene>
<cutsceneid="bigopenworldskip"explanation="">
<dialoguespeaker="purple"english="I'll be right here if you need any help!"translation="سأنتظرك هنا في حال ما احتجت أي مساعدة!"/>
</cutscene>
<cutsceneid="talkpurple_intro"explanation="">
<dialoguespeaker="player"english="I'm feeling a bit overwhelmed, Doctor."translation="المهمة ضخمة، يا دكتورة."/>
<dialoguespeaker="player"english="Where do I begin?"translation="من أين أبدأ؟"/>
<dialoguespeaker="purple"english="Remember that you can press {b_map} to check where you are on the map!"translation="هل نسيت؟ يمكنك بضغط {b_map} التأكد من مكانك في الخريطة!"buttons="1"/>
<dialoguespeaker="purple"english="Look for areas where the rest of the crew might be..."translation="أقترح عليك البحث في الأماكن التي ربما نقل إليها بقية أفراد طاقمنا..."/>
<dialoguespeaker="purple"english="If you get lost, you can get back to the ship from any teleporter."translation="ولو ضعت، يمكنك العودة للسفينة من أي آلة تنقيل."/>
<dialoguespeaker="purple"english="And don't worry! We'll find everyone!"translation="ولا تشغلن بالك! مهما حصل سنعثر على الجميع!"/>
<dialoguespeaker="purple"english="Everything will be ok!"translation="وكل شيء سيمر على ما يرام...!"/>
</cutscene>
<cutsceneid="talkpurple_3"explanation="only one player string is shown">
<dialoguespeaker="purple"english="Are you doing ok, Captain?"translation="هل أنت بخير يا قبطان؟"/>
<dialoguespeaker="player"english="I'm worried about Victoria, Doctor!"translation="يقلقني أمر فيكتوريا يا دكتورة!"/>
<dialoguespeaker="player"english="I'm worried about Vitellary, Doctor!"translation="يقلقني أمر فيتيلاري يا دكتورة!"/>
<dialoguespeaker="player"english="I'm worried about Verdigris, Doctor!"translation="يقلقني أمر فيرديغري يا دكتورة!"/>
<dialoguespeaker="player"english="I'm worried about Vermilion, Doctor!"translation="يقلقني أمر فارميليون يا دكتورة!"/>
<dialoguespeaker="player"english="I'm worried about you, Doctor!"translation="يقلقني أمرك يا دكتورة!"/>
<dialoguespeaker="purple"english="Oh - well, don't worry, they'll show up!"translation="أوه، لا داع للقلق. عن قريب ستلتقيان بلا شك!"/>
<dialoguespeaker="purple"english="Here! Have a lollipop!"translation="هاك! مصاصة هدية مني!"/>
</cutscene>
<cutsceneid="trinketcollector"explanation="if no trinkets found yet">
<dialoguespeaker="cyan"english="This seems like a good place to store anything I find out there..."translation="يبدو مكانا مناسبا لتخزين أي شيء ألقاه..."/>
<dialoguespeaker="cyan"english="Victoria loves to study the interesting things we find on our adventures!"translation="كانت فيكتوريا مهتمة بدراسة ما نجلبه لها، كلما وجدنا ما يثير الاهتمام أثناء مغامراتنا!"/>
</cutscene>
<cutsceneid="newtrinketcollector"explanation="if at least one trinket found">
<dialoguespeaker="cyan"english="This seems like a good place to store those shiny things."translation="يبدو المكان مستودعا مناسبا لتلك الأغراض اللماعة."/>
<dialoguespeaker="cyan"english="Victoria loves to study the interesting things we find on our adventures!"translation="كانت فيكتوريا مهتمة بدراسة ما نجلبه لها، كلما وجدنا ما يثير الاهتمام أثناء مغامراتنا!"/>
</cutscene>
<cutsceneid="new2trinketcollector"explanation="">
<dialoguespeaker="cyan"english="I hope she's ok..."translation="أرجو أنها بخير..."/>
</cutscene>
<cutsceneid="rescuegreen"explanation="nuance: `she's BACK on the ship` means more `she's home` and not `she has returned to the ship`. She has never left the ship">
<dialoguespeaker="green"english="Captain! I've been so worried!"translation="قبطان! كم كنت قلقا!"/>
<dialoguespeaker="green"english="I've been trying to get out, but I keep going around in circles..."translation="حاولت الخروج، لكني كنت أدور وأدور وأعود من حيث بدأت..."/>
<dialoguespeaker="player"english="I've come from the ship. I'm here to teleport you back to it."translation="جئتك من السفينة. أتيت كي أرجعك إليها بآلة التنقيل."/>
<dialoguespeaker="green"english="Is everyone else alright? Is Violet..."translation="هل الجمع بخير؟ ماذا عن فايوليت، أهي..."/>
<dialoguespeaker="player"english="She's fine - she's back on the ship!"translation="لا تقلق عليها، عادت سالمة معافاة للسفينة!"/>
<dialoguespeaker="green"english="Oh! Great - Let's get going, then!"translation="أوه! عظيم إذن - فلننطلق دونما تأجيل!"/>
</cutscene>
<cutsceneid="rescueblue"explanation="">
<dialoguespeaker="blue"english="Oh no! Captain! Are you stuck here too?"translation="قبطان! مصيبة! حتى أنت علقت هنا؟"/>
<dialoguespeaker="player"english="It's ok - I'm here to rescue you!"translation="لا بأس - جئت لإنقاذك!"/>
<dialoguespeaker="player"english="Let me explain everything..."translation="فلتسمحي لي أن أشرح لك كل شيء..."/>
<dialoguespeaker="blue"english="What? I didn't understand any of that!"translation="كيف...؟ لم أفهم حرفا من كلامك!"/>
<dialoguespeaker="player"english="Oh... well, don't worry."translation="أوه... معذرة، المهم لا تقلقي."/>
<dialoguespeaker="player"english="Follow me! Everything will be alright!"translation="اتبعيني! ولا خوف عليك!"/>
<dialoguespeaker="red"english="Am I ever glad to see you! I thought I was the only one to escape the ship..."translation="ما أسعدني برؤيتك حقا! ظننت أني الناجي الوحيد من السفينة..."/>
<dialoguespeaker="player"english="Vermilion! I knew you'd be ok!"translation="فارميليون! لم يراودني شك أنك ستنجو!"/>
<dialoguespeaker="red"english="So, what's the situation?"translation="لكن، كيف الأوضاع؟"/>
<dialoguespeaker="red"english="I see! Well, we'd better get back then."translation="فهمت... إذن فلنتحرك ونرجع."/>
<dialoguespeaker="red"english="There's a teleporter in the next room."translation="توجد آلة تنقيل في الغرفة المجاورة."/>
</cutscene>
<cutsceneid="rescueyellow"explanation="">
<dialoguespeaker="yellow"english="Ah, Viridian! You got off the ship alright too?"translation="أه ، فيريديان! حتى أنت نجوت بدون إصابات من حادث السفينة؟"/>
<dialoguespeaker="player"english="It's good to see you're alright, Professor!"translation="أستاذ؟ يسعدني أنك بخير!"/>
<dialoguespeaker="yellow"english="Is the ship ok?"translation="هل السفينة بخير؟"/>
<dialoguespeaker="player"english="It's badly damaged, but Violet's been working on fixing it."translation="تعرضت لأضرار بليغة، لكن فايولت تعمل على إصلاحها."/>
<dialoguespeaker="player"english="We could really use your help..."translation="ستفيدنا مساعدتك جدا..."/>
<dialoguespeaker="yellow"english="Ah, of course!"translation="أه، عن طيب خاطر!"/>
<dialoguespeaker="yellow"english="The background interference in this dimension prevented the ship from finding a teleporter when we crashed!"translation="منع تشويش المحيط في نطاق هذا البعد سفينتنا من إيجاد آلة تنقيل قبيل الارتطام!"/>
<dialoguespeaker="yellow"english="We've all been teleported to different locations!"translation="تفرقنا ونقلنا إلى أماكن مختلفة!"/>
<dialoguespeaker="player"english="Er, that sounds about right!"translation="همم، يبدو لي كلامك منطقيا!"/>
<dialoguespeaker="yellow"english="Let's get back to the ship, then!"translation="فلنرجع إلى السفينة!"/>
<dialoguespeaker="yellow"english="After you, Captain!"translation="من بعدك يا قبطان!"/>
<dialoguespeaker="player"english="Something's gone wrong... We should look for a way back!"translation="حصلت مشكلة... فلنبحث عن طريق العودة!"/>
</cutscene>
<cutsceneid="int1blue_2"explanation="">
<dialoguespeaker="player"english="Follow me! I'll help you!"translation="اتبعيني! سأنقذك!"/>
<dialoguespeaker="blue"english="Promise you won't leave without me!"translation="أرجوك! أريد منك وعدا بعدم المغادرة وتركي!"/>
<dialoguespeaker="player"english="I promise! Don't worry!"translation="وعد! لا تقلقي!"/>
</cutscene>
<cutsceneid="int1blue_3"explanation="">
<dialoguespeaker="player"english="Are you ok down there, Doctor?"translation="هل نزلت على خير يا دكتورة؟"/>
<dialoguespeaker="blue"english="I wanna go home!"translation="آه، أريد العودة للديار!"/>
<dialoguespeaker="blue"english="Where are we? How did we even get here?"translation="أين نحن؟ كيف جئنا هنا؟"/>
<dialoguespeaker="player"english="Well, Violet did say that the interference in the dimension we crashed in was causing problems with the teleporters..."translation="صحيح، قالت فايولت أن في هذا البعد تشويشا يعرقل عمل آلات التنقيل..."/>
<dialoguespeaker="player"english="I guess something went wrong..."translation="فأظن مشكلة حصلت في الأثناء..."/>
<dialoguespeaker="player"english="But if we can find another teleporter, I think we can get back to the ship!"translation="لكن لو عثرنا على آلة تنقيل بديلة، يمكننا العودة للسفينة على ما أظن!"/>
<dialoguespeaker="blue"english="Captain! Captain! Wait for me!"translation="قبطان! قبطان! أرجوك الانتظار!"/>
<dialoguespeaker="blue"english="Please don't leave me behind! I don't mean to be a burden!"translation="أرجوك، لا أريد أن تتخلى عني! لا أقصد أن أكون عبئا عليك!"/>
<dialoguespeaker="blue"english="I'm scared!"translation="كل ما في الأمر... أني خائفة!"/>
<dialoguespeaker="player"english="Oh... don't worry Victoria, I'll look after you!"translation="أوه... لا تقلقي يا فيكتوريا، سأعتني بك!"/>
</cutscene>
<cutsceneid="int1blue_5"explanation="">
<dialoguespeaker="blue"english="We're never going to get out of here, are we?"translation="لن نخرج من هنا أبدا، أليس كذلك؟"/>
<dialoguespeaker="player"english="I.. I don't know..."translation="أنا... لا أدري..."/>
<dialoguespeaker="player"english="I don't know where we are or how we're going to get out..."translation="لا أعرف أين نحن، ولا كيف سنخرج..."/>
</cutscene>
<cutsceneid="int1blue_6"explanation="">
<dialoguespeaker="blue"english="We're going to be lost forever!"translation="سنضيع فيها إلى الأبد!"/>
<dialoguespeaker="player"english="Ok, come on... Things aren't that bad."translation="لحظة، صبرا... لا داع للتشاؤم إلى هذا الحد."/>
<dialoguespeaker="player"english="I have a feeling that we're nearly home!"translation="عندي إحساس أننا اقتربنا من طريق العودة للديار!"/>
<dialoguespeaker="player"english="We can't be too far from another teleporter!"translation="لسنا بعيدين عن آلة تنقيل أخرى بلا شك!"/>
<dialoguespeaker="blue"english="I hope you're right, captain..."translation="أتمنى أنك على حق يا قبطان..."/>
</cutscene>
<cutsceneid="int1blue_7"explanation="">
<dialoguespeaker="blue"english="Captain! You were right! It's a teleporter!"translation="قبطان! كلامك كان صحيحا! توجد آلة تنقيل!"/>
<dialoguespeaker="player"english="Phew! You had me worried for a while there... I thought we were never going to find one."translation="هوف! تنفست الصعداء بعد طول قلق... خشيت أننا لن نجد آلة تنقيل أخرى."/>
<dialoguespeaker="player"english="Anyway, let's go back to the ship."translation="على كل! فلنعد إلى السفينة."/>
</cutscene>
<cutsceneid="int1green_1"explanation="">
<dialoguespeaker="green"english="Huh? This isn't the ship..."translation="هاه؟ ليست هذه السفينة..."/>
<dialoguespeaker="green"english="Captain! What's going on?"translation="قبطان! ما الخطب؟"/>
<dialoguespeaker="player"english="I... I don't know!"translation="أنا... لا أعرف السبب!"/>
<dialoguespeaker="player"english="Where are we?"translation="أين نحن الآن؟"/>
<dialoguespeaker="green"english="Uh oh, this isn't good... Something must have gone wrong with the teleporter!"translation="أوه لا، لا يسر هذا... حصلت مشكلة في آلة التنقيل بلا شك!"/>
<dialoguespeaker="player"english="Ok... no need to panic!"translation="طيب... لا داعي للجزع!"/>
<dialoguespeaker="player"english="Let's look for another teleporter!"translation="فلنبحث عن آلة تنقيل أخرى!"/>
</cutscene>
<cutsceneid="int1green_2"explanation="">
<dialoguespeaker="player"english="Let's go this way!"translation="من هذا الطريق!"/>
<dialoguespeaker="green"english="After you, Captain!"translation="معك يا قبطان!"/>
</cutscene>
<cutsceneid="int1green_3"explanation="just like in rescuegreen, `Violet is back on the ship` does not mean she was ever off the ship and has *returned* to it. Or maybe Verdigris thinks Violet was warped off the ship as well just like him and she *has* made her way back to the ship?">
<dialoguespeaker="green"english="So Violet's back on the ship? She's really ok?"translation="إذن فايوليت عادت للسفينة؟ حقا هي بخير؟"/>
<dialoguespeaker="player"english="She's fine! She helped me find my way back!"translation="اطمئن! هي من ساعدتني على الاهتداء لطريق العودة!"/>
<dialoguespeaker="green"english="Oh, phew! I was worried about her."translation="أوه، هوف! خفت عليها."/>
<dialoguespeaker="green"english="Captain, I have a secret..."translation="قبطان، سأبوح لك بسر..."/>
<dialoguespeaker="green"english="I really like Violet!"translation="حقا أحب فايولت!"/>
<dialoguespeaker="player"english="Is that so?"translation="أحقا هذا؟"/>
<dialoguespeaker="green"english="Please promise you won't tell her!"translation="أترجاك عدم البوح بهذا السر أمامها!"/>
<dialoguespeaker="player"english="Are you doing ok?"translation="أأنت بخير؟"/>
<dialoguespeaker="green"english="I think so! I really hope we can find a way back to the ship..."translation="أظن! أرجو حقا أن نجد طريق العودة للسفينة..."/>
</cutscene>
<cutsceneid="int1green_5"explanation="">
<dialoguespeaker="green"english="So, about Violet..."translation="أردت سؤالك يا قبطان، بخصوص فايولت..."/>
<dialoguespeaker="player"english="So, do you think you'll be able to fix the ship?"translation="بالمناسبة، هل تقدر على إصلاح السفينة؟"/>
<dialoguespeaker="green"english="Depends on how bad it is... I think so, though!"translation="حسب فداحة الأضرار... لكني أظن أني على قدر المهمة!"/>
<dialoguespeaker="green"english="It's not very hard, really. The basic dimensional warping engine design is pretty simple, and if we can get that working we shouldn't have any trouble getting home."translation="ليس الأمر من الصعوبة بمكان. أما عن أساس فكرة التصميم لمحرك التنقيل ما بين الأبعاد، فمسألته بسيطة، وإن شغلناه فلن نلقى عقبة في العودة للديار."/>
<dialoguespeaker="green"english="Finally! A teleporter!"translation="أخيرا! آلة تنقيل!"/>
<dialoguespeaker="green"english="I was getting worried we wouldn't find one..."translation="بدأت أقلق وأتساءل ماذا لو لم نجدها..."/>
<dialoguespeaker="player"english="Let's head back to the ship!"translation="فلنرجع إلى السفينة!"/>
</cutscene>
<cutsceneid="int1red_1"explanation="">
<dialoguespeaker="red"english="Wow! Where are we?"translation="واو! أين نحن؟"/>
<dialoguespeaker="player"english="This... isn't right... Something must have gone wrong with the teleporter!"translation="لا... لا يصح هذا... حصلت مشكلة في آلة التنقيل بدون شك!"/>
<dialoguespeaker="red"english="Oh well... We can work it out when we get back to the ship!"translation="حقا... لكن لا بأس، سنتوصل إلى جواب بعد أن نرجع إلى السفينة!"/>
<dialoguespeaker="red"english="Let's go exploring!"translation="فلننطلق في رحلة استكشاف جديدة!"/>
<dialoguespeaker="player"english="Ok then!"translation="هيا بنا إذن!"/>
<dialoguespeaker="red"english="Aye aye, Captain!"translation="لا يأمر عليك ظالم، يا قبطان!"/>
</cutscene>
<cutsceneid="int1red_3"explanation="">
<dialoguespeaker="red"english="Hey Viridian... how did the crash happen, exactly?"translation="ها فيريديان... كيف حصل حادث الارتطام بالضبط؟"/>
<dialoguespeaker="player"english="Oh, I don't really know - some sort of interference..."translation="أوه، لا أدري عن التفاصيل - كأنها إشارة تشويش..."/>
<dialoguespeaker="player"english="...or something sciencey like that. It's not really my area."translation="...أو كلام علماء من هذا النوع. خارج عن تخصصي."/>
<dialoguespeaker="red"english="Ah! Well, do you think we'll be able to fix the ship and go home?"translation="أه! طيب، هل تظننا سنتمكن من إصلاح السفينة والعودة للديار؟"/>
<dialoguespeaker="player"english="Of course! Everything will be ok!"translation="طبعا بلا شك! ستحل كل الأمور!"/>
<dialoguespeaker="red"english="Hi again! You doing ok?"translation="أهلا مجددا! أحوالك بخير؟"/>
<dialoguespeaker="player"english="I think so! But I really want to get back to the ship..."translation="أظن! لكني بدأت أشتاق جدا للسفينة..."/>
<dialoguespeaker="red"english="We'll be ok! If we can find a teleporter somewhere we should be able to get back!"translation="لا خوف علينا! لو عثرنا على آلة تنقيل سنتمكن من العودة!"/>
</cutscene>
<cutsceneid="int1red_5"explanation="">
<dialoguespeaker="red"english="Are we there yet?"translation="هل وصلنا أم ما زلنا؟"/>
<dialoguespeaker="player"english="We're getting closer, I think..."translation="أظننا نقترب..."/>
<dialoguespeaker="player"english="I wonder where we are, anyway?"translation="يا ترى أين نحن أصلا؟"/>
<dialoguespeaker="player"english="This seems different from that dimension we crashed in, somehow..."translation="يبدو هذا النطاق مختلفا عن البعد الذي سقطنا فيه، لا أدري كيف..."/>
<dialoguespeaker="red"english="I dunno... But we must be close to a teleporter by now..."translation="ما أدراني... لكن لا شك أننا اقتربنا من آلة تنقيل بعد هذا المسير..."/>
<dialoguespeaker="red"english="See? I told you! Let's get back to the ship!"translation="رأيت؟ قلت لك! فلنرجع للسفينة!"/>
</cutscene>
<cutsceneid="int1yellow_1"explanation="">
<dialoguespeaker="yellow"english="Oooh! This is interesting..."translation="أوووه! مثير..."/>
<dialoguespeaker="yellow"english="Captain! Have you been here before?"translation="قبطان! هل سبق وجئت إلى هنا؟"/>
<dialoguespeaker="player"english="What? Where are we?"translation="ما هذا... أين نحن؟"/>
<dialoguespeaker="yellow"english="I suspect something deflected our teleporter transmission! This is somewhere new..."translation="يخامرني شك أن شيئا ما أثر في بث تنقيلتنا وأحادها عن مسارها! هذا المكان جديد علينا..."/>
<dialoguespeaker="player"english="We should try to find a teleporter and get back to the ship..."translation="فلنحاول أن نجد آلة تنقيل حتى نرجع للسفينة..."/>
<dialoguespeaker="yellow"english="Right behind you, Captain!"translation="على خطاك نسير، يا قبطان!"/>
</cutscene>
<cutsceneid="int1yellow_3"explanation="">
<dialoguespeaker="player"english="What do you make of all this, Professor?"translation="...بروفيسور، ما استنتاجاتك؟"/>
<dialoguespeaker="yellow"english="I'm guessing this dimension has something to do with the interference that caused us to crash!"translation="أخمن أن لهذا البعد يدا في سبب التشويش الذي سبب حادث ارتطام سفينتنا!"/>
<dialoguespeaker="yellow"english="Maybe we'll find the cause of it here?"translation="لعلنا نلقى أصل السبب هنا؟"/>
<dialoguespeaker="yellow"english="Well, it's just a guess. I'll need to get back to the ship before I can do any real tests..."translation="لكنه تخمين لا أكثر. أحتاج العودة للسفينة قبل أن أجري أي اختبارات جادة..."/>
</cutscene>
<cutsceneid="int1yellow_4"explanation="the split paths merge, and Vitellary sees a checkpoint">
<dialoguespeaker="yellow"english="Ohh! What was that?"translation="أووو! ما هذا؟"/>
<dialoguespeaker="player"english="What was what?"translation="ما هذا الذي تقول عنده ما هذا؟"/>
<dialoguespeaker="yellow"english="That big... C thing! I wonder what it does?"translation="تلك... الحاء الكبيرة! في ذلك الشيء! ماذا يفعل يا ترى؟"/>
<dialoguespeaker="player"english="Em... I don't really know how to answer that question..."translation="احم... لا أعرف كيف أجيبك على سؤالك هذا..."/>
<dialoguespeaker="player"english="It's probably best not to acknowledge that it's there at all."translation="الأفضل أن نتجاهل وجوده، ونمر عليه مرور الكرام."/>
<dialoguespeaker="yellow"english="Maybe we should take it back to the ship to study it?"translation="ألا يجدر بنا جلبه للسفينة حتى نجري عليه دراسات؟"/>
<dialoguespeaker="player"english="We really shouldn't think about it too much... Let's keep moving!"translation="حقا من الأفضل ألا نفرط في التفكير في أمره... ولنتحرك!"/>
</cutscene>
<cutsceneid="int1yellow_5"explanation="">
<dialoguespeaker="yellow"english="You know, there's something really odd about this dimension..."translation="لو علمت بم أفكر الآن... أحس أن في هذا البعد أمرا في غاية الغرابة..."/>
<dialoguespeaker="yellow"english="We shouldn't really be able to move between dimensions with a regular teleporter..."translation="لا يفترض بنا أن نتمكن من التنقل بين الأبعاد بمجرد آلة تنقيل عادية..."/>
<dialoguespeaker="yellow"english="Maybe this isn't a proper dimension at all?"translation="ألا يحتمل إذن أن هذا ليس ببعد قائم الذات؟"/>
<dialoguespeaker="yellow"english="Maybe it's some kind of polar dimension? Something artificially created for some reason?"translation="ربما هو بعد قطبي، أو شيء من ذلك النوع؟ بمعنى أنه شيء كونوه اصطناعيا لسبب من الأسباب؟"/>
<dialoguespeaker="yellow"english="I can't wait to get back to the ship. I have a lot of tests to run!"translation="لا أطيق الصبر على العودة للسفينة. كم عندي اختبارات كثيرة أجريها!"/>
</cutscene>
<cutsceneid="int1yellow_6"explanation="">
<dialoguespeaker="yellow"english="I wonder if there's anything else in this dimension worth exploring?"translation="أتساءل هل في هذا البعد مكانا آخرا يستحق الاستطلاع؟"/>
<dialoguespeaker="player"english="Maybe... but we should probably just focus on finding the rest of the crew for now..."translation="ربما... لكن يجدر بنا أن نركز على إنجاد بقية أفراد طاقمنا، ثم للحديث بقية..."/>
<dialoguespeaker="yellow"english="That was interesting, wasn't it?"translation="ألم تكن نزهة مثيرة للحماس؟"/>
<dialoguespeaker="player"english="I feel dizzy..."translation="آه، يا للدوار..."/>
</cutscene>
<cutsceneid="talkpurple_1"explanation="">
<dialoguespeaker="purple"english="... I hope Verdigris is alright."translation="... أرجو أن فارديغري بخير."/>
<dialoguespeaker="purple"english="If you can find him, he'd be a big help fixing the ship!"translation="ليتك وجدته، لأنه سيكون عونا كبيرا في إصلاح السفينة!"/>
</cutscene>
<cutsceneid="talkpurple_2"explanation="">
<dialoguespeaker="purple"english="Chief Verdigris is so brave and ever so smart!"translation="كم القائد فيرديغري شجاع، وشهم، وذكي على الدوام!"/>
</cutscene>
<cutsceneid="talkpurple_4"explanation="">
<dialoguespeaker="purple"english="Welcome back, Captain!"translation="أهلا بعودتك بالسلامة يا قبطان!"/>
<dialoguespeaker="purple"english="I think Victoria is quite happy to be back on the ship."translation="أحسب أن فيكتوريا سعيدة جدا بعودتها للسفينة."/>
<dialoguespeaker="purple"english="She really doesn't like adventuring. She gets very homesick!"translation="لا تحب أجواء المغامرات كثيرا. لأنها سرعان ما تحن للبيت!"/>
</cutscene>
<cutsceneid="talkpurple_5"explanation="only one of the last 6 strings is shown">
<dialoguespeaker="purple"english="Vermilion called in to say hello!"translation="اتصل فارميليون، ويسلم عليك!"/>
<dialoguespeaker="purple"english="He's really looking forward to helping you find the rest of the crew!"translation="يتوق حقا لمساعدتك في إيجاد بقية الطاقم!"/>
<dialoguespeaker="purple"english="He's really looking forward to helping you find Victoria!"translation="يتوق حقا لمساعدتك في إيجاد فيكتوريا!"/>
<dialoguespeaker="purple"english="He's really looking forward to helping you find Vitellary!"translation="يتوق حقا لمساعدتك في إيجاد فيتيلاري!"/>
<dialoguespeaker="purple"english="He's really looking forward to helping you find Verdigris!"translation="يتوق حقا لمساعدتك في إيجاد فارديغري!"/>
<dialoguespeaker="purple"english="He's really looking forward to helping you find Vermilion!"translation="يتوق حقا لمساعدتك في إيجاد فارميليون!"/>
<dialoguespeaker="purple"english="He's really looking forward to helping you find you!"translation="يتوق حقا لمساعدتك في التوصل لإيجاد مكانك!"/>
</cutscene>
<cutsceneid="talkpurple_6"explanation="">
<dialoguespeaker="purple"english="Captain! You found Verdigris!"translation="قبطان! عثرت على فارديغري!"/>
<dialoguespeaker="purple"english="Thank you so much!"translation="حقا أشكرك! شكرا جزيلا!"/>
</cutscene>
<cutsceneid="talkpurple_7"explanation="">
<dialoguespeaker="purple"english="I'm glad Professor Vitellary is ok!"translation="سعيدة أن البروفيسور فيتيلاري بخير!"/>
<dialoguespeaker="purple"english="He had lots of questions for me about this dimension."translation="سألني أسئلة كثيرة عن هذا البعد."/>
<dialoguespeaker="purple"english="He's already gotten to work with his research!"translation="وسرعان ما عاد إلى العمل على أبحاثه!"/>
<dialoguespeaker="player"english="Doctor, something strange happened when we teleported back to the ship..."translation="دكتور، حصل شيء غريب عندما انتقلنا راجعين للسفينة..."/>
<dialoguespeaker="player"english="We got lost in another dimension!"translation="تهنا في بعد آخر!"/>
<dialoguespeaker="purple"english="Maybe that dimension has something to do with the interference that caused us to crash here?"translation="ربما لذلك البعد دخل في التشويش الذي تسبب في حادث ارتطامنا؟"/>
<dialoguespeaker="purple"english="I'll look into it..."translation="سأنظر في الموضوع..."/>
<dialoguespeaker="player"english="Doctor, something strange has been happening when we teleport back to the ship..."translation="دكتور، حصل شيء غريب عندما انتقلنا راجعين للسفينة..."/>
<dialoguespeaker="player"english="We keep getting brought to another weird dimension!"translation="يوجد بعد غريب، وأرغمنا مجددا على الانتقال إليه!"/>
<dialoguespeaker="purple"english="Maybe that dimension has something to do with the interference that caused us to crash here?"translation="ربما لذلك البعد دخل في التشويش الذي تسبب في حادث ارتطامنا؟"/>
<dialoguespeaker="purple"english="Hmm, there's definitely something strange happening..."translation="همممم، بالتأكيد يحصل شيء غريب..."/>
<dialoguespeaker="purple"english="If only we could find the source of that interference!"translation="ليتنا نتوصل إلى مصدر ذلك التشويش!"/>
</cutscene>
<cutsceneid="talkpurple_8"explanation="">
<dialoguespeaker="purple"english="Hey Captain! Now that you've turned off the source of the interference, we can warp everyone back to the ship instantly, if we need to!"translation="هيه يا قبطان! بعد أن أغلقت مصدر التشويش، صرنا نستطيع أن ننقل الجميع إلى متن السفينة فور ما نشاء، وقتما نحتاج!"/>
<dialoguespeaker="purple"english="Any time you want to come back to the ship, just select the new SHIP option in your menu!"translation="متى أردت العودة للسفينة، يمكنك اختيار خيار السفينة في القائمة!"/>
</cutscene>
<cutsceneid="talkgreen_1"explanation="">
<dialoguespeaker="green"english="I'm an engineer!"translation="أنا مهندس!"/>
</cutscene>
<cutsceneid="talkgreen_2"explanation="">
<dialoguespeaker="green"english="I think I can get this ship moving again, but it's going to take a while..."translation="أستطيع جعل السفينة تتحرك ثانية، لكن عملي سيستغرق فترة..."/>
</cutscene>
<cutsceneid="talkgreen_3"explanation="">
<dialoguespeaker="green"english="Victoria mentioned something about a lab? I wonder if she found anything down there?"translation="حكت لك فايوليت عن مخبر؟ ترى هل وجدت فيه شيئا؟"/>
</cutscene>
<cutsceneid="talkgreen_4"explanation="">
<dialoguespeaker="green"english="Vermilion's back! Yey!"translation="فارميليون عاد! يا لفرحتي، ياي!!"/>
</cutscene>
<cutsceneid="talkgreen_5"explanation="">
<dialoguespeaker="green"english="The Professor had lots of questions about this dimension for me..."translation="سألني البروفيسور أسئلة كثيرة بخصوص هذا البعد..."/>
<dialoguespeaker="green"english="We still don't really know that much, though."translation="ومع هذا لا زلنا لا نعرف الكثير."/>
<dialoguespeaker="green"english="Until we work out what's causing that interference, we can't go anywhere."translation="إلى أن نتوصل لمعرفة ما الذي يتسبب بالتداخل، لن نستطيع التحرك قيد أنملة."/>
</cutscene>
<cutsceneid="talkgreen_6"explanation="">
<dialoguespeaker="green"english="I'm so glad that Violet's alright!"translation="كم أنا سعيد أن فايوليت بخير!"/>
</cutscene>
<cutsceneid="talkgreen_7"explanation="">
<dialoguespeaker="green"english="That other dimension we ended up in must be related to this one, somehow..."translation="هل تذكرت ذلك البعد البديل الذي انتهى المطاف بنا إليه؟ أظن بينه وهذا البعد علاقة ما..."/>
</cutscene>
<cutsceneid="talkgreen_8"explanation="">
<dialoguespeaker="green"english="The antenna's broken! This is going to be very hard to fix..."translation="الهوائي مكسور! سيصعب التصليح جدا..."/>
</cutscene>
<cutsceneid="talkgreen_9"explanation="">
<dialoguespeaker="green"english="It looks like we were warped into solid rock when we crashed!"translation="يبدو أننا عندما ارتطمنا هنا، حصلت تنقيلة للسفينة إلى داخل كتلة صخرية!"/>
<dialoguespeaker="green"english="Hmm. It's going to be hard to separate from this..."translation="هممم. سيصعب فصل السفينة عنها..."/>
</cutscene>
<cutsceneid="talkgreen_10"explanation="">
<dialoguespeaker="green"english="The ship's all fixed up. We can leave at a moment's notice!"translation="أصلحت السفينة بأكملها. يمكننا المغادرة في أي لحظة، والأمر رهن إشارتك!"/>
</cutscene>
<cutsceneid="talkred_1"explanation="">
<dialoguespeaker="red"english="Don't worry, Sir!"translation="لا قلق، يا فندم!"/>
<dialoguespeaker="red"english="We'll find a way out of here!"translation="سنبحث عن طريق للخروج من هنا!"/>
</cutscene>
<cutsceneid="talkred_2"explanation="">
<dialoguespeaker="red"english="I hope Victoria is ok..."translation="أرجو أن فيكتوريا بخير..."/>
<dialoguespeaker="red"english="She doesn't handle surprises very well..."translation="أعرفها، ولا تتحمل المفاجآت جيدا..."/>
</cutscene>
<cutsceneid="talkred_3"explanation="">
<dialoguespeaker="red"english="I don't know how we're going to get this ship working again!"translation="لا أدري كيف سنتصرف لتشغيل هذه السفينة مجددا!"/>
<dialoguespeaker="red"english="Chief Verdigris would know what to do..."translation="لو كان القائد فارديغري هنا، لكان يعرف الحل..."/>
</cutscene>
<cutsceneid="talkred_4"explanation="">
<dialoguespeaker="red"english="I wonder what caused the ship to crash here?"translation="يا ترى ما الذي سبب ارتطام السفينة في هذا المكان؟"/>
<dialoguespeaker="red"english="It's the shame the Professor isn't here, huh? I'm sure he could work it out!"translation="خسارة أن البروفيسور ليس هنا. أليس كذلك؟ أنا متأكد أنه لن يحير في إيجاد الجواب!"/>
</cutscene>
<cutsceneid="talkred_5"explanation="">
<dialoguespeaker="red"english="It's great to be back!"translation="ما أحلى العودة!"/>
<dialoguespeaker="red"english="I can't wait to help you find the rest of the crew!"translation="أتوق بفارغ الصبر لمساعدتك على إيجاد بقية الطاقم!"/>
<dialoguespeaker="red"english="It'll be like old times, huh, Captain?"translation="مثل الأيام الخوالي، أليس كذلك يا قبطان؟"/>
</cutscene>
<cutsceneid="talkred_6"explanation="">
<dialoguespeaker="red"english="It's good to have Victoria back with us."translation="جيد أن فيكتوريا عادت إلينا."/>
<dialoguespeaker="red"english="She really seems happy to get back to work in her lab!"translation="بدت لي في غاية السعادة عندما رجعت للعمل في مخبرها!"/>
</cutscene>
<cutsceneid="talkred_7"explanation="">
<dialoguespeaker="red"english="I think I saw Verdigris working on the outside of the ship!"translation="أظن أني رأيت فارديغري منهمكا في العمل خارج السفينة!"/>
</cutscene>
<cutsceneid="talkred_8"explanation="">
<dialoguespeaker="red"english="You found Professor Vitellary! All right!"translation="وجدت البروفيسور فيتيلاري! أحلى الأخبار!"/>
<dialoguespeaker="red"english="We'll have this interference thing worked out in no time now!"translation="ما دام الأمر هكذا، فسرعان ما سنحل مشكلة التشويش هذه!"/>
</cutscene>
<cutsceneid="talkred_9"explanation="">
<dialoguespeaker="red"english="That other dimension was really strange, wasn't it?"translation="كان ذلك البعد البديل في غاية الغرابة، أليس كذلك؟"/>
<dialoguespeaker="red"english="I wonder what caused the teleporter to send us there?"translation="ترى ما الذي جعل آلة التنقيل ترسلنا إلى هناك؟"/>
</cutscene>
<cutsceneid="talkred_10"explanation="">
<dialoguespeaker="red"english="Heya Captain!"translation="ويه يا قبطان!"/>
<dialoguespeaker="red"english="This way looks a little dangerous..."translation="هذا الطريق يبدو لي خطيرا قليلا..."/>
<dialoguespeaker="red"english="Hey Captain!"translation="أهلا يا قبطان!"/>
<dialoguespeaker="red"english="I found something interesting around here - the same warp signature I saw when I landed!"translation="عثرت على شيء مثير للاهتمام هنا - نفس بصمة الطاقة لآلة التنقيل التي رأيتها عندما نزلت!"/>
<dialoguespeaker="red"english="Someone from the ship must be nearby..."translation="لا بد أن أحد أفراد طاقم السفينة قربنا..."/>
</cutscene>
<cutsceneid="talkred_13"explanation="">
<dialoguespeaker="red"english="This dimension is pretty exciting, isn't it?"translation="هذا البعد حافل بالتشويق والإثارة، صح أو لا؟"/>
<dialoguespeaker="red"english="I wonder what we'll find?"translation="يا ترى ماذا سنلقى؟"/>
</cutscene>
<cutsceneid="talkblue_1"explanation="">
<dialoguespeaker="blue"english="Any signs of Professor Vitellary?"translation="هل من علامات تدل على مكان البروفيسور فيتيلاري؟"/>
<dialoguespeaker="player"english="Sorry, not yet..."translation="معذرة، لم أجده بعد..."/>
<dialoguespeaker="blue"english="I hope he's ok..."translation="أتمنى أنه بخير..."/>
</cutscene>
<cutsceneid="talkblue_2"explanation="">
<dialoguespeaker="blue"english="Thanks so much for saving me, Captain!"translation="شكرا من القلب لإنقاذي يا قبطان!"/>
</cutscene>
<cutsceneid="talkblue_3"explanation="">
<dialoguespeaker="blue"english="I'm so glad to be back!"translation="كم أنا سعيدة بعودتي!"/>
<dialoguespeaker="blue"english="That lab was so dark and scary! I didn't like it at all..."translation="كم كان ذلك المخبر مظلما... موحشا... مخيفا! لم أستلطفه إطلاقا..."/>
</cutscene>
<cutsceneid="talkblue_4"explanation="">
<dialoguespeaker="blue"english="Vitellary's back? I knew you'd find him!"translation="عاد فيتيلاري؟ كنت واثقة أنكما ستلتقيان!"/>
<dialoguespeaker="blue"english="I mean, I admit I was very worried that you wouldn't..."translation="بل أعترف... أني خفت جدا أنك ربما ذهبت ورجعت دون إيجاده..."/>
<dialoguespeaker="blue"english="or that something might have happened to him..."translation="أو أن مكروها حصل له، حاشا وكلا..."/>
<dialoguespeaker="player"english="Doctor Victoria? He's ok!"translation="دكتورة فيكتوريا؟ هوني عليك. لم يحصل شر له، فلا داعي للقلق!"/>
<dialoguespeaker="blue"english="Oh! Sorry! I was just thinking about what if he wasn't?"translation="أوه! متأسفة! تخيلت أنه لم يكن بخير، ماذا كنت لأفعل وقتها؟"/>
<dialoguespeaker="blue"english="Thank you, Captain!"translation="أشكرك جزيلا، يا قبطان!"/>
</cutscene>
<cutsceneid="talkblue_5"explanation="">
<dialoguespeaker="blue"english="You found Vermilion! Great!"translation="وجدت فارميليون! هذا خبر رائع!"/>
<dialoguespeaker="blue"english="I wish he wasn't so reckless!"translation="ليته يقلل من طيشه!"/>
<dialoguespeaker="blue"english="He'll get himself into trouble..."translation="سيودي به تهوره إلى مشكلة كبيرة..."/>
</cutscene>
<cutsceneid="talkblue_6"explanation="">
<dialoguespeaker="blue"english="Verdigris is ok! Violet will be so happy!"translation="فارديغري بخير! ستسعد فايولت كثيرا!"/>
<dialoguespeaker="blue"english="Though I was very worried..."translation="مع ذلك كنت قلقة جدا..."/>
</cutscene>
<cutsceneid="talkblue_7"explanation="">
<dialoguespeaker="blue"english="Why did the teleporter send us to that scary dimension?"translation="لماذا أرسلتنا آلة التنقيل تلك إلى ذلك البعد الموحش؟"/>
<dialoguespeaker="blue"english="What happened?"translation="ماذا حصل وقتها؟"/>
<dialoguespeaker="player"english="I don't know, Doctor..."translation="لا أدري يا دكتورة..."/>
<dialoguespeaker="blue"english="Heya Captain!"translation="يا أهلا يا قبطان!"/>
<dialoguespeaker="blue"english="Are you going to try and find the rest of these shiny things?"translation="هل نويت محاولة جمع بقية هذه الأغراض اللماعة؟"/>
</cutscene>
<cutsceneid="talkblue_trinket1"explanation="">
<dialoguespeaker="blue"english="Hey Captain, I found this in that lab..."translation="أهلا يا قبطان، وجدت هذا في ذلك المخبر..."/>
<dialoguespeaker="blue"english="Any idea what it does?"translation="هل عندك فكرة بم يفيدنا؟"/>
<dialoguespeaker="player"english="Sorry, I don't know!"translation="أتأسف، لا أدري!"/>
<dialoguespeaker="player"english="They seem important, though..."translation="لكنه يبدو مهما..."/>
<dialoguespeaker="player"english="Maybe something will happen if we find them all?"translation="ربما لو جمعناهم كلهم يحصل شيء؟"/>
</cutscene>
<cutsceneid="talkblue_trinket2"explanation="">
<dialoguespeaker="blue"english="Captain! Come have a look at what I've been working on!"translation="قبطان! عندي شيء كنت أعمل عليه، لو أردت رؤيته هيا!"/>
<dialoguespeaker="blue"english="It looks like these shiny things are giving off a strange energy reading!"translation="يبدو أن تلك اللماعات الغريبات ترصد منها إشارة طاقة غريبة!"/>
<dialoguespeaker="blue"english="So I analysed it..."translation="ولهذا السبب أجريت دراسة تحليلية عليها..."/>
</cutscene>
<cutsceneid="talkblue_trinket3"explanation="">
<dialoguespeaker="blue"english="Captain! Come have a look at what I've been working on!"translation="قبطان! عندي شيء كنت أعمل عليه، لو أردت رؤيته هيا!"/>
<dialoguespeaker="blue"english="I found this in that lab..."translation="وجدت هذا في ذلك المخبر..."/>
<dialoguespeaker="blue"english="It seemed to be giving off a weird energy reading..."translation="مما بدا لي، صدر منه إشارة طاقة غريبة..."/>
<dialoguespeaker="blue"english="So I analysed it..."translation="ولهذا السبب أجريت دراسة تحليلية عليه..."/>
</cutscene>
<cutsceneid="talkblue_trinket4"explanation="">
<dialoguespeaker="blue"english="...and I was able to find more of them with the ship's scanner!"translation="...واستطعت أن أجد المزيد منهم بفضل ماسحة السفينة!"/>
<dialoguespeaker="blue"english="If you get a chance, it might be worth finding the rest of them!"translation="لو سنحت لك الفرصة، ربما يستحق العناء جمع بقية تلك الأغراض!"/>
<dialoguespeaker="blue"english="Don't put yourself in any danger, though!"translation="لكن... إياك وتعريض نفسك للأخطار!"/>
</cutscene>
<cutsceneid="talkblue_trinket5"explanation="">
<dialoguespeaker="blue"english="...but it looks like you've already found all of them in this dimension!"translation="...إلا أنك وجدتها كلها في هذا البعد قبل حديثنا!"/>
<dialoguespeaker="blue"english="Yeah, well done! That can't have been easy!"translation="أجل، أحسنت حقا! لا أظن ذلك كان بالمهمة السهلة!"/>
</cutscene>
<cutsceneid="talkblue_trinket6"explanation="">
<dialoguespeaker="blue"english="...and they're related. They're all a part of something bigger!"translation="...كما أن علاقة تجمع بينها. كل منها [بعض] من [كل]!"/>
<dialoguespeaker="blue"english="Yeah! There seem to be twenty variations of the fundamental energy signature..."translation="أجل! يبدو أن للإشارة الأساسية الطاقية عشرون نوعا بتغييرات طفيفة..."/>
<dialoguespeaker="blue"english="Does that mean you've found all of them?"translation="هل عنيت بكلامك أنك وجدتها كلها؟"/>
</cutscene>
<cutsceneid="talkyellow_1"explanation="">
<dialoguespeaker="yellow"english="I'm making some fascinating discoveries, captain!"translation="توصلت إلى اكتشافات مدهشة، يا قبطان!"/>
</cutscene>
<cutsceneid="talkyellow_2"explanation="">
<dialoguespeaker="yellow"english="This isn't like any other dimension we've been to, Captain."translation="هذا البعد لا يشبه الأبعاد الأخرى التي زرناها يا قبطان."/>
<dialoguespeaker="yellow"english="There's something strange about this place..."translation="في هذا المكان أمر غريب..."/>
</cutscene>
<cutsceneid="talkyellow_3"explanation="">
<dialoguespeaker="yellow"english="Captain, have you noticed that this dimension seems to wrap around?"translation="قبطان، هل لاحظت أن هذا البعد يبدو وكأنه يلتف حول نفسه؟"/>
<dialoguespeaker="yellow"english="It looks like this dimension is having the same stability problems as our own!"translation="يبدو أن هذا البعد يتشاطر مع بعدنا في ديارنا نفس مشاكل الاستقرار!"/>
<dialoguespeaker="yellow"english="I hope we're not the ones causing it..."translation="أرجو أننا لسنا المتسببين بذلك..."/>
<dialoguespeaker="player"english="What? Do you think we might be?"translation="ماذا؟ أتظننا سببا؟"/>
<dialoguespeaker="yellow"english="No no... that's very unlikely, really..."translation="لا لا... ذلك احتمال ضئيل، لا يؤخذ به حقا..."/>
</cutscene>
<cutsceneid="talkyellow_4"explanation="">
<dialoguespeaker="yellow"english="My guess is that whoever used to live here was experimenting with ways to stop the dimension from collapsing."translation="حسب تخميناتي، فمن كانوا يعيشون هنا أجروا تجارب كثيرة وبحثوا في طرق إيقاف انهيار هذا البعد."/>
<dialoguespeaker="yellow"english="It would explain why they've wrapped the edges..."translation="يفسر هذا لماذا حاولوا ربط الحواف ببعضها..."/>
<dialoguespeaker="yellow"english="Hey, maybe that's what's causing the interference?"translation="هيه، ربما ذلك ما يسبب التشويش؟"/>
</cutscene>
<cutsceneid="talkyellow_5"explanation="">
<dialoguespeaker="yellow"english="I wonder where the people who used to live here have gone?"translation="أتساءل إلى أين سافر الناس الذين كانوا يعيشون هنا؟"/>
</cutscene>
<cutsceneid="talkyellow_6"explanation="">
<dialoguespeaker="yellow"english="I think it's no coincidence that the teleporter was drawn to that dimension..."translation="لا أظنها صدفة أن آلة التنقيل استقطبتنا إلى هذا البعد..."/>
<dialoguespeaker="yellow"english="There's something there. I think it might be causing the interference that's stopping us from leaving..."translation="يوجد هناك شيء. وأظنه السبب في التشويش الذي يمنعنا من المغادرة..."/>
</cutscene>
<cutsceneid="talkyellow_7"explanation="">
<dialoguespeaker="yellow"english="I'm glad Verdigris is alright."translation="سعيد أن فارديغري بخير."/>
<dialoguespeaker="yellow"english="It'll be a lot easier to find some way out of here now that we can get the ship working again!"translation="سيسهل كثيرا إيجاد مخرج من هنا بعد أن صارت السفينة تعمل!"/>
</cutscene>
<cutsceneid="talkyellow_8"explanation="">
<dialoguespeaker="yellow"english="Ah, you've found Doctor Victoria? Excellent!"translation="أه، وجدت الدكتورة فيكتوريا! أخبار رائعة!"/>
<dialoguespeaker="yellow"english="I have lots of questions for her!"translation="عندي أسئلة كثيرة أطرحها عليها!"/>
</cutscene>
<cutsceneid="talkyellow_9"explanation="">
<dialoguespeaker="yellow"english="Vermilion says that he was trapped in some sort of tunnel?"translation="قال فارميليون أنه كان عالقا في نفق ما؟"/>
<dialoguespeaker="player"english="Yeah, it just seemed to keep going and going..."translation="أجل، بدا وكأنه يستمر بلا نهاية..."/>
<dialoguespeaker="yellow"english="Interesting... I wonder why it was built?"translation="معلومة تفيد... يا ترى ما سبب بنائه على تلك الحالة؟"/>
</cutscene>
<cutsceneid="talkyellow_10"explanation="">
<dialoguespeaker="yellow"english="It's good to be back!"translation="ما أحلى العودة!"/>
<dialoguespeaker="yellow"english="I've got so much work to catch up on..."translation="عندي شغل كثير فاتني يجب أن أتداركه..."/>
</cutscene>
<cutsceneid="talkyellow_11"explanation="">
<dialoguespeaker="yellow"english="I know it's probably a little dangerous to stay here now that this dimension is collapsing..."translation="أعرف أن البقاء في هذا البعد، وهو آهل للانهيار، مجازفة بعض الشيء..."/>
<dialoguespeaker="yellow"english="...but it's so rare to find somewhere this interesting!"translation="...لكن يندر أن تجد في الحياة أمرا مثيرا للاهتمام بنفس القدر!"/>
<dialoguespeaker="yellow"english="Maybe we'll find the answers to our own problems here?"translation="ربما قد نجد هنا حلولا للمشاكل التي اعترضتنا في ديارنا؟"/>
</cutscene>
<cutsceneid="talkyellow_trinket1"explanation="">
<dialoguespeaker="yellow"english="Captain! I've been meaning to give this to you..."translation="قبطان! أردت أن أعطيك هذا..."/>
<dialoguespeaker="player"english="Professor! Where did you find this?"translation="بروفيسور! من أين لك هذا؟"/>
<dialoguespeaker="yellow"english="Oh, it was just lying around that space station."translation="أوه، كان غرضا منسيا في تلك المحطة الفضائية."/>
<dialoguespeaker="yellow"english="It's a pity Doctor Victoria isn't here, she loves studying that sort of thing..."translation="خسارة أن فيكتوريا ليست هنا، لأنها تعشق دراسة تلك الأشياء..."/>
<dialoguespeaker="player"english="Any idea what it does?"translation="هل عندك فكرة عن فائدته؟"/>
<dialoguespeaker="yellow"english="Nope! But it is giving off a strange energy reading..."translation="أبدا! لكنه يصدر إشارة طاقة غريبة.."/>
</cutscene>
<cutsceneid="talkyellow_trinket2"explanation="">
<dialoguespeaker="yellow"english="...so I used the ship's scanner to find more of them!"translation="...ولهذا السبب استغليت راصدات السفينة كي أبحث عن المزيد منه!"/>
<dialoguespeaker="yellow"english="...Please don't let them distract you from finding Victoria, though!"translation="...لكن أتمنى ألا يشتت هذا اهتمامك عن إنقاذ فيكتوريا!"/>
<dialoguespeaker="yellow"english="I hope she's ok..."translation="أرجو أنها بخير..."/>
</cutscene>
<cutsceneid="talkyellow_trinket3"explanation="">
<dialoguespeaker="yellow"english="Can't seem to detect any more of them nearby, though."translation="لم أعد أستطيع استكشاف المزيد منه على مقربة منا."/>
<dialoguespeaker="yellow"english="Maybe you've found them all?"translation="ربما يعني هذا أنك وجدتهم كلهم؟"/>
<dialoguespeaker="cyan"english="Aha! This must be what's causing the interference!"translation="آ-هاه! هذا مسبب التشويش بلا شك!"/>
<dialoguespeaker="cyan"english="I wonder if I can turn it off?"translation="هل يمكنني إطفاؤه يا ترى؟"/>
<dialoguespeaker="gray"english="WARNING: Disabling the Dimensional Stability Generator may lead to instability! Are you sure you want to do this?"translation="تحذير:تعطيلعملفارضاستقرارالبعدقديؤديلحصولاختلالفيالاستقرار!أواثقأنهذهنيتكحقا؟
Are you really sure you want to do this?" translation="ويحك أرجوك! قد يودي هذا بهذا البعد برمته إلى الانهيار الشامل! هلا تعقلت؟ هلا فكرت في التبعات ببعض الجدية، ولو لبرهة؟ ما هو قرارك؟
<dialoguespeaker="blue"english="I knew you'd be ok!"translation="كنت على ثقة بنجاتك!"/>
<dialoguespeaker="purple"english="We were very worried when you didn't come back..."translation="قلقنا كثيرا عندما تأخرت عودتك..."/>
<dialoguespeaker="green"english="...but when you turned off the source of the interference..."translation="...لكن عندما أطفأت أنت مصدر التشويش..."/>
<dialoguespeaker="yellow"english="...we were able to find you with the ship's scanners..."translation="...تمكننا براصدات السفينة من تحديد مكانك..."/>
<dialoguespeaker="red"english="...and teleport you back on board!"translation="...وقمنا بتنقيلك على متن السفينة!"/>
<dialoguespeaker="player"english="That was lucky!"translation="ذلك من حسن حظي!"/>
<dialoguespeaker="player"english="Thanks guys!"translation="شكرا يا جماعة!"/>
<dialoguespeaker="yellow"english="...it looks like this dimension is starting to destabilise, just like our own..."translation="...يبدو أن استقرار هذا البعد يختل، مثل ما حصل مع بعدنا..."/>
<dialoguespeaker="red"english="...we can stay and explore for a little longer, but..."translation="...يمكننا أن نبقى قليلا بعد هنا لاستطلاع المكان..."/>
<dialoguespeaker="yellow"english="...eventually, it'll collapse completely."translation="...لكن ستأتي لحظة ينهار فيها هذا البعد برمته."/>
<dialoguespeaker="green"english="There's no telling exactly how long we have here. But the ship's fixed, so..."translation="لا سبيل لنحدد بالضبط كم المهلة الزمنية المتبقية لنا هنا. لكن سفينتنا أصلحت على كل حال..."/>
<dialoguespeaker="blue"english="...as soon as we're ready, we can go home!"translation="...لذا، فور أن نستعد، نستطيع العودة لديارنا!"/>
<dialoguespeaker="purple"english="What now, Captain?"translation="والآن يا قبطان، ماذا نحن فاعلون؟"/>
<dialoguespeaker="player"english="Let's find a way to save this dimension!"translation="فلنبحث عن طريقة لإنقاذ هذا البعد!"/>
<dialoguespeaker="player"english="And a way to save our home dimension too!"translation="وكذلك... عن طريقة لإنقاذ بعدنا الأصلي!"/>
<dialoguespeaker="player"english="The answer is out there, somewhere!"translation="الجواب ينتظرنا في مكان ما!"/>
<dialoguespeaker="blue"english="Wow! You found all of them!"translation="واو! وجدتها كلها!"/>
<dialoguespeaker="player"english="Really? Great!"translation="صحيح؟ هذا خبر عظيم!"/>
<dialoguespeaker="blue"english="I'll run some tests and see if I can work out what they're for..."translation="سأجري بعض الاختبارات كي أحدد ما فائدتها..."/>
<dialoguespeaker="player"english="That... that didn't sound good..."translation="ليس... ليس هذا مطمئنا..."/>
<dialoguespeaker="red"english="Not again!"translation="آه ليس مجددا!"/>
<dialoguespeaker="player"english="Wait! It's stopped!"translation="صبرا! توقف الصوت!"/>
<dialoguespeaker="purple"english="This is where we were storing those shiny things? What happened?"translation="هنا كنا نخزن تلك الأغراض اللماعة؟ ماذا حصل لها؟"/>
<dialoguespeaker="player"english="We were just playing with them, and..."translation="كنا نعبث بها فحسب، وإذ بها..."/>
<dialoguespeaker="purple"english="Or, you know... we could have just warped back to the ship..."translation="هل تعلمون... عوض هذا، كنا نستطيع التنقل للسفينة..."/>
<dialoguespeaker="green"english="Wow! What is this?"translation="واو! ما هذا؟"/>
<dialoguespeaker="yellow"english="It looks like another laboratory!"translation="كأنه مخبر آخر!"/>
<dialoguespeaker="red"english="Let's have a look around!"translation="فلنلق نظرة في الأنحاء!"/>
</cutscene>
<cutsceneid="talkpurple_9"explanation="">
<dialoguespeaker="purple"english="Look at all this research! This is going to be a big help back home!"translation="يا سلام، هل رأيت كل هذه الأبحاث؟ ستفيدنا فائدة جمة لو رجعنا بها للوطن!"/>
</cutscene>
<cutsceneid="talkgreen_11"explanation="">
<dialoguespeaker="green"english="I wonder why they abandoned this dimension? They were so close to working out how to fix it..."translation="يا ترى لماذا هجروا هذا البعد؟ مع أنهم أوشكوا على اكتشاف الحل لإصلاحه..."/>
<dialoguespeaker="green"english="Maybe we can fix it for them? Maybe they'll come back?"translation="ربما نصلحه نيابة عنهم؟ ربما يعودون بعد ذلك؟"/>
</cutscene>
<cutsceneid="talkblue_9"explanation="">
<dialoguespeaker="blue"english="This lab is amazing! The scientists who worked here know a lot more about warp technology than we do!"translation="مخبر رائع! علماؤه يعرفون أكثر منا بكثير عن تقنيات علوم التنقيل المكاني!"/>
</cutscene>
<cutsceneid="talkyellow_12"explanation="">
<dialoguespeaker="yellow"english="Captain! Have you seen this?"translation="قبطان! هل سبق ورأيت هذا؟"/>
<dialoguespeaker="yellow"english="With their research and ours, we should be able to stabilise our own dimension!"translation="لو جمعنا بين أبحاثهم وأبحاثنا، سنتوصل للحل الذي يحقق استقرار بعد أوطاننا!"/>
<dialoguespeaker="red"english="Look what I found!"translation="شاهدوا ماذا وجدت!"/>
<dialoguespeaker="red"english="It's pretty hard, I can only last for about 10 seconds..."translation="اللعبة صعبة جدا، لم أستطع الصمود أكثر من 10 ثوان..."/>
</cutscene>
<cutsceneid="terminal_jukebox"explanation="">
<dialoguespeaker="gray"english="-=JUKEBOX =-
Songs will continue to play until you leave the ship.
Collect trinkets to unlock new songs!" translation="-= مشغل الموسيقى =-
يستمر عزف الأغنية حتى مغادرة السفينة.
كلما جمعت تذكارات، فتحت أغان جديدة!" centertext="1" padtowidth="264"/>
</cutscene>
<cutsceneid="terminal_jukeunlock1"explanation="">
<dialoguespeaker="gray"english="NEXTUNLOCK:
5 Trinkets
Pushing Onwards" translation="الشرط: 5 تذكارات
يفتح الأغنية القادمة
Pushing Onwards" tt="1" pad="1"/>
</cutscene>
<cutsceneid="terminal_jukeunlock2"explanation="">
<dialoguespeaker="gray"english="NEXTUNLOCK:
8 Trinkets
Positive Force" translation="الشرط: 8 تذكارات
يفتح الأغنية القادمة
Positive Force" tt="1" pad="1"/>
</cutscene>
<cutsceneid="terminal_jukeunlock3"explanation="">
<dialoguespeaker="gray"english="NEXTUNLOCK:
10 Trinkets
Presenting VVVVVV" translation="الشرط: 10 تذكارات
يفتح الأغنية القادمة
Presenting VVVVVV" tt="1" pad="1"/>
</cutscene>
<cutsceneid="terminal_jukeunlock4"explanation="">
<dialoguespeaker="gray"english="NEXTUNLOCK:
12 Trinkets
Potential for Anything" translation="الشرط: 12 تذكار
<dialoguespeaker="gray"english="-= PERSONAL LOG =-"translation="-= مذكرات شخصية =-"padtowidth="280"/>
<dialoguespeaker="gray"english="Almost everyone has been evacuated from the space station now. The rest of us are leaving in a couple of days, once our research has been completed."translation="أخلينا الجميع من المحطة الفضائية. سيغادر من تبقى منا بعد يومين، عقب انتهاء أبحاثنا. ؝"pad="1"/>
</cutscene>
<cutsceneid="terminal_station_2"explanation="">
<dialoguespeaker="gray"english="-= Research Notes =-"translation="-= ملاحظات الباحثين =-"padtowidth="264"/>
<dialoguespeaker="gray"english="...everything collapses, eventually. It's the way of the universe."translation="...كل شيء سائر إلى الانهيار، طال الزمان أو قصر. هكذا سنة الحياة في الكون. ؝"centertext="1"pad="1"/>
</cutscene>
<cutsceneid="terminal_station_3"explanation="">
<dialoguespeaker="gray"english="I wonder if the generator we set up in the polar dimension is what's affecting our teleporters?"translation="أتساءل... هل للمولد الذي شغلناه في البعد القطبي دخل في التأثير الطارئ على آلات تنقيلنا؟"/>
<dialoguespeaker="gray"english="No, it's probably just a glitch."translation="لا أظن، ذلك مجرد عيب تقني خفيف على الأرجح. ؝"/>
</cutscene>
<cutsceneid="terminal_station_4"explanation="a trinket that's difficult to get">
<dialoguespeaker="gray"english="-= PERSONAL LOG =-"translation="-= مذكرات شخصية =-"padtowidth="280"/>
<dialoguespeaker="gray"english="Hah! Nobody will ever get this one."translation="هاع! أتحدى أي مخلوق أن يفوز بهذه بالذات. ؝"pad="1"/>
</cutscene>
<cutsceneid="terminal_warp_1"explanation="">
<dialoguespeaker="gray"english="...The other day I was chased down a hallway by a giant cube with the word AVOID on it."translation="... قبل كم يوم، طاردني في أحد الأروقة مكعب عملاق يتدحرج وعليه كلمة [تفادى]."/>
<dialoguespeaker="gray"english="These security measures go too far!"translation="بالغوا حقا في إجراءات الحماية!! ؝"/>
</cutscene>
<cutsceneid="terminal_warp_2"explanation="">
<dialoguespeaker="gray"english="The only way into my private lab anymore is by teleporter."translation="لم تبق أي طريقة لدخول مخبري الخاص سوى آلة التنقيل."/>
<dialoguespeaker="gray"english="I've made sure that it's difficult for unauthorised personnel to gain access."translation="حرصت أن أصعب الدخول على غير العمال المسموح لهم. ؝"/>
</cutscene>
<cutsceneid="terminal_outside_1"explanation="">
<dialoguespeaker="gray"english="-= Research Notes =-"translation="-= ملاحظات الباحثين =-"padtowidth="264"/>
<dialoguespeaker="gray"english="... our first breakthrough was the creation of the inversion plane, which creates a mirrored dimension beyond a given event horizon ..."translation="... إنجازنا وسبقنا الأول بدأ من إنشاء مستوي التعاكس، مما من شأنه إنشاء بعد معكوس جديد عند زمكان ما بعد نقطة أفق حدث معروفة في معطياتنا ..."pad="1"/>
</cutscene>
<cutsceneid="terminal_outside_2"explanation="">
<dialoguespeaker="gray"english="-= Research Notes =-"translation="-= ملاحظات الباحثين =-"padtowidth="264"/>
<dialoguespeaker="gray"english="...with just a small modification to the usual parameters, we were able to stabilise an infinite tunnel!"translation="...بإدخال تعديل بسيط على المتغيرات المعتادة، نجحنا في الحفاظ على استقرار نفق لا متناه! ؝"/>
</cutscene>
<cutsceneid="terminal_outside_3"explanation="">
<dialoguespeaker="gray"english="-= Research Notes =-"translation="-= ملاحظات الباحثين =-"padtowidth="264"/>
<dialoguespeaker="gray"english="... the final step in creating the dimensional stabiliser was to create a feedback loop ..."translation="... وآخر خطوة لإنشاء فارض استقرار البعد أن نصنع حلقة ارتجاع ..."pad="1"/>
</cutscene>
<cutsceneid="terminal_outside_4"explanation="">
<dialoguespeaker="gray"english="-= Research Notes =-"translation="-= ملاحظات الباحثين =-"padtowidth="264"/>
<dialoguespeaker="gray"english="...despite our best efforts, the dimensional stabiliser won't hold out forever. Its collapse is inevitable..."translation="... مهما بذلنا من جهود، لن يتحمل فارض توازن البعد إلى ما لا نهاية. انهياره محتوم عاجلا أم آجلا..."pad="1"/>
<dialoguespeaker="cyan"english="Huh? These coordinates aren't even in this dimension!"translation="عجبا؟ هذه الإحداثيات خارجة عن نطاق هذا البعد!"/>
</cutscene>
<cutsceneid="terminal_outside_5"explanation="">
<dialoguespeaker="gray"english="-= Personal Log =-"translation="-= مذكرات شخصية =-"padtowidth="232"/>
<dialoguespeaker="gray"english="... I've had to seal off access to most of our research. Who knows what could happen if it fell into the wrong hands? ..."translation="... اضطررت لتعطيل الدخول إلى معظم أبحاثنا. من يدري ماذا قد يحصل لو وقعت في أيادي السوء؟ ..."centertext="1"pad="1"/>
</cutscene>
<cutsceneid="terminal_outside_6"explanation="">
<dialoguespeaker="gray"english="-= Research Notes =-"translation="-= ملاحظات الباحثين =-"padtowidth="264"/>
<dialoguespeaker="gray"english="... access to the control center is still possible through the main atmospheric filters ..."translation="... الدخول إلى مركز التحكم لا يزال ممكنا لو كان عبر مصافي الأغلفة الجوية الأساسية ..."/>
</cutscene>
<cutsceneid="terminal_lab_1"explanation="">
<dialoguespeaker="gray"english="... it turns out the key to stabilising this dimension was to create a balancing force outside of it!"translation="... اتضح أن الحل لاستقرار هذا البعد يكمن في إنشاء قوة خارجه لإحلال التوازن!"/>
<dialoguespeaker="gray"english="Though it looks like that's just a temporary solution, at best."translation="إلا أن هذا مجرد حل وقتي، ولن يدوم حتى في أحسن الظروف."/>
<dialoguespeaker="gray"english="I've been working on something more permanent, but it seems it's going to be too late..."translation="حاولت العمل على حل دائم يرسخ مفعوله، لكن يبدو لي أن الأوان سيفوت قبل تطبيقه. ؝"/>
<dialoguespeaker="player"english="Now that the ship is fixed, we can leave anytime we want!"translation="بعد أن أصلحنا السفينة، يمكننا المغادرة متى شئنا!"/>
<dialoguespeaker="player"english="We've all agreed to keep exploring this dimension, though."translation="لكننا اتفقنا جميعا أن نواصل استكشاف هذا البعد."/>
<dialoguespeaker="player"english="Who knows what we'll find?"translation="من يدري ماذا قد نجد؟"/>
</cutscene>
<cutsceneid="terminal_radio"explanation="">
<dialoguespeaker="gray"english="-=SHIPRADIO =-
[ Status ]
Broadcasting" translation="-= راديو السفينة =-
[ الوضع الحالي ]
الإرسال قيد البث" tt="1" centertext="1" pad="2"/>
</cutscene>
<cutsceneid="terminal_secretlab"explanation="">
<dialoguespeaker="gray"english="-=WARNING =-
The Super-Gravitron is intended for entertainment purposes only." translation="-= تحذير =-
<dialoguespeaker="gray"english="Anyone found using the Super Gravitron for educational purposes may be asked to stand in the naughty corner."translation="لو افتضح أمر شخص يستخدمه لأغراض تعليمية، سنطلب منه أن يبكي في زاوية العقاب."/>
<dialoguespeaker="cyan"english="...oh, I've already found this."translation="...أوه، سبق أن وجدته."/>
</cutscene>
<cutsceneid="disableaccessibility"explanation="">
<dialoguespeaker="gray"english="Please disable invincibility and/or slowdown before entering the Super Gravitron."translation="يرجى تعطيل الحصانة وتبطئة اللعبة قبل دخول غرفة أتون الجاذبية الخارق."/>
<!-- You can translate these in-game to get better context! See README.txt -->
<roomnames>
<roomnamex="0"y="18"english="Single-slit Experiment"translation="التجربة 1: ثغرة مفردة"explanation="(Many of the rooms in the Lab stage have science themed names.)"/>
<roomnamex="0"y="19"english="Don't Flip Out"translation="لا ينقلبن مزاجك"explanation="Flip as in gravity flip, but also the expression in english, as in, keep your cool"/>
<roomnamex="1"y="0"english="I'm Sorry"translation="آسف"explanation="The room below this one is Please Forgive Me. There is also a secret path to the right which leads to the rooms 'Anomaly' and 'Purest Unobtainium'."/>
<roomnamex="1"y="1"english="Please Forgive Me!"translation="سامحني أرجوك!"explanation="The room above this one is I'm Sorry"/>
<roomnamex="1"y="17"english="Rascasse"translation="الأسماك الشائكة"explanation="This is a type of fish with lots of thorny spikes on its back"/>
<roomnamex="1"y="18"english="Keep Going"translation="نواصل"explanation="Literally just 'Keep Going', through the room"/>
<roomnamex="1"y="19"english="Shuffled Hallway"translation="رواق منحاز"explanation="Just describes how the room looks. 'Shuffled' as in offset."/>
<roomnamex="2"y="0"english="Kids His Age Bounce"translation="الصبيان في عمره ينطون"explanation="An old saying for when e.g. a small child falls out of a tree. A trampoline joke in this case."/>
<roomnamex="2"y="1"english="Playing Foosball"translation="كرة قدم الطاولة"explanation="This room resembles a Foosball table - https://en.wikipedia.org/wiki/Table_football"/>
<roomnamex="2"y="4"english="Philadelphia Experiment"translation="تجربة فيلاديلفيا"explanation="There is a teleporter in this room. The Philadelphia Experiment is the name of 80's film about teleportation."/>
<roomnamex="2"y="16"english="Get Ready To Bounce"translation="نستعد لننط"explanation="The first room in the Lab zone. In the next room, you immediately come into contact with a gravity line, and flip back up."/>
<roomnamex="2"y="17"english="It's Perfectly Safe"translation="كله أمان فعليك بالاطمئنان"explanation="The second room in the lab zone. Don't worry, it's perfectly safe."/>
<roomnamex="2"y="18"english="Young Man, It's Worth the Challenge"translation="بني، التحدي يستحق المجهود"explanation="Not a reference to anything in particular - Bennett explains that this is just something that his high school chemistry teacher used to say to students. He thinks the teacher was probably misquoting George Bernard Shaw, who said 'Life is not meant to be easy, my child - but take courage: it can be delightful.'."/>
<roomnamex="2"y="19"english="Double-slit Experiment"translation="التجربة 2: ثغرة مثناة"explanation="(Many of the rooms in the Lab stage have science themed names.)"/>
<roomnamex="3"y="0"english="Merge"translation="التحام"explanation="A wide section connecting to a narrower section - merge as in traffic"/>
<roomnamex="3"y="1"english="A Difficult Chord"translation="عزفت على الوتر الحساس"explanation="This room resembles a guitar chord"/>
<roomnamex="3"y="4"english="Why So Blue?"translation="يا نهار أزرق! لماذا الحزن؟"explanation="The crewmate Victoria is found here. Victoria is always sad! She's feeling blue."/>
<roomnamex="3"y="16"english="Brought to you by the letter G"translation="برعاية الحرف G"explanation="Reference to Seseme Street - also, this room resembles the letter G"/>
<roomnamex="3"y="17"english="Thorny Exchange"translation="تراشق إبر الكلام"explanation="Two people having a polite argument could be described as having a Thorny Exchange of words."/>
<roomnamex="3"y="18"english="Square Root"translation="الجذر التربيعي"explanation="This room resembles a Square Root symbol."/>
<roomnamex="3"y="19"english="They Call Him Flipper"translation="صديق الإنسان، شقلوب"explanation="This is a line from an American TV show intro about a Dolphin - https://www.youtube.com/watch?v=azEOeTX1LqM"/>
<roomnamex="4"y="0"english="Vibrating String Problem"translation="إشكالية الوتر المتذبذب"explanation="Another science themed room name, this one has two gravity lines that you bounce between."/>
<roomnamex="4"y="1"english="The Living Dead End"translation="الطرف المستطرف المتطرف"explanation="'The living end' is an idiom meaning 'the most extreme form of something', here it's an extreme dead end, i.e. a cul-de-sac"/>
<roomnamex="4"y="2"english="AAAAAA"translation="آآآآآآ"explanation="The player falls through this room without having time to stop - AAAAAA suggests a scream in English. Also, it's six As, like the title."/>
<roomnamex="4"y="3"english="Diode"translation="صمام"explanation="This room can only be passed through in one direction. It resembles the electrical component."/>
<roomnamex="4"y="4"english="I Smell Ozone"translation="شممت الأوزون"explanation="When you use a photocopier, it produces a distinctive smell (from the Ozone produced). This room has a background pattern that suggests a teleportation has recently happened here."/>
<roomnamex="4"y="16"english="Free Your Mind"translation="حرر دماغك"explanation="Reference to the film The Matrix, where Morpheus jumps from the top of a Skyscaper - https://www.youtube.com/watch?v=ef_agVIvh0A"/>
<roomnamex="4"y="17"english="I Changed My Mind, Thelma..."translation="بدلت رأيي يا ثيلما..."explanation="The room below Free Your Mind. Reference to the film Thelma and Louise, which ends with Thelma and Louise driving off a cliff, sorry, spoilers"/>
<roomnamex="4"y="18"english="Hitting the Apex"translation="ذروة المنعطف"explanation="'Hitting the Apex' is the term used by race drivers for the optimal path around a corner"/>
<roomnamex="4"y="19"english="Three's a Crowd"translation="سر الثلاثة سر الجمهرة"explanation="From the expression Two's Company, Three's a Crowd. This room has two challenges - the first has two gaps to cross, the second has three."/>
<roomnamex="5"y="0"english="Spike Strip Deployed"translation="فلتنشر الأشواك"explanation="This room has some spikes on a gravity line. The name is a reference to the device that police might use to blow out the tyres of a speeding car."/>
<roomnamex="5"y="1"english="Anomaly"translation="الظاهرة الغريبة"explanation="As in, a strange result in science. This room has lots of different colours, unlike other rooms in this stage."/>
<roomnamex="5"y="16"english="In a Single Bound"translation="بوثبة واحدة"explanation="Superman is described as being able to leap tall buildings in a single bound."/>
<roomnamex="5"y="17"english="Indirect Jump Vector"translation="متجه قفزة غير مباشرة"explanation="If you miss the gap in 'In a Single Bound' above, you will end up back in this room - hence, your trajectory was off!"/>
<roomnamex="6"y="0"english="Topsy Turvyism"translation="عاليها سافلها"explanation="Topsy Turvy is Australian slang for upside down"/>
<roomnamex="6"y="1"english="Purest Unobtainium"translation="معدن المجهولنديوم الخالص"explanation="Unobtainium is a jokey made up term from science fiction for an impossible substance - https://en.wikipedia.org/wiki/Unobtainium"/>
<roomnamex="6"y="16"english="Barani, Barani"translation="شقلبة، شقلبتان"explanation="A Barani is a technical term for doing a flip on a trampoline"/>
<roomnamex="6"y="17"english="Safety Dance"translation="رقصة الأمان"explanation="Named after the 80s song by Men Without Hats."/>
<roomnamex="7"y="0"english="Standing Wave"translation="الموجة الموقوفة"explanation="Many of the rooms in the Lab stage have science themed names. This one is at the beginning of a section with gravity lines above and below you, before the section begins."/>
<roomnamex="7"y="15"english="Entanglement Generator"translation="كم تشابكا ستولد بآلتك؟"explanation="This room contains a teleporter. Entanglement is an idea from quantum mechanics."/>
<roomnamex="7"y="16"english="Heady Heights"translation="الرأس الأعلى"explanation="Just below the highest point in the level."/>
<roomnamex="7"y="17"english="Exhausted?"translation="النشاط العادم"explanation="This room has an exit that sort of suggests an Exhaust Pipe in a car."/>
<roomnamex="7"y="18"english="The Tantalizing Trinket"translation="سأتذكر التذكار"explanation="You see this Trinket just out of reach as you fall through the room."/>
<roomnamex="7"y="19"english="The Bernoulli Principle"translation="مبدأ بيرنولي"explanation="Many of the rooms in the Lab stage have science themed names - this one is just named after a formula relating to flight."/>
<roomnamex="8"y="9"english="Teleporter Divot"translation="أثر التنقيلة"explanation="There is a pattern in the background of this room that indicates that a teleporter has sent someone to this room"/>
<roomnamex="9"y="9"english="The Tower"translation="البرج"explanation="This room is a single vertically scrolling stage, about 20 rooms high. The name is from the Tarot card."/>
<roomnamex="10"y="4"english="Seeing Red"translation="العين الحمراء"explanation="This is the room that you find the red crewmate in. (Seeing Red is an expression in English about being filled with rage, but that doesn't really apply here)"/>
<roomnamex="10"y="5"english="Energize"translation="تنشيط"explanation="There is a teleporter in this Room. Energize is what they say on Star Trek when they use the teleporters."/>
<roomnamex="10"y="6"english="Down Under"translation="تحت أم الدنيا في آخر الدنيا"explanation="Australia is sometimes called The Land Down Under because of its position on a globe. You complete this room by going down and under some moving platforms"/>
<roomnamex="10"y="7"english="A Deception"translation="أخدوعة"explanation="This room appears trivial at first, but is connected to a difficult trinket challenge"/>
<roomnamex="11"y="4"english="Building Apport"translation="توطيد الروابط الزمكانية"explanation="An 'Apport' is a kind of paranormal teleportation. Building Apport is a pun on the concept of 'building rapport' (except that to 'apport' is to teleport)."/>
<roomnamex="11"y="5"english="Frown Upside Down"translation="اقلب عبوس حواجبك"explanation="To 'Turn that Frown Upside Down' is an expression in English, like 'cheer up', basically means to stop being unhappy"/>
<roomnamex="11"y="6"english="Shenanigan"translation="ألعوبة"explanation="Shenanigan as in a prank, or practical joke. This room is connected to the 'Prize for the Reckless' puzzle, and like the room 'A Deception', it appears trivial unless you know the secret"/>
<roomnamex="11"y="7"english="Prize for the Reckless"translation="جائزة للمتهورين"explanation="This room contains a trinket that can only be collected by doing something difficult"/>
<roomnamex="11"y="11"english="Conveying a New Idea"translation="توصيل الفكرة ببساط-ة"explanation="This is the first room you encounter that has conveyor belts in it"/>
<roomnamex="11"y="12"english="One Way Room"translation="دخول الحمام ليس كخروجه"explanation="Can only be travelled through in one direction"/>
<roomnamex="11"y="13"english="Boldly To Go"translation="بجرأة فلنمض"explanation="Star Trek reference, this rooms is near the entrance to the space station level"/>
<roomnamex="11"y="14"english="The Filter"translation="الغربال"explanation="Like a filter from an air conditioning vent"/>
<roomnamex="12"y="3"english="Security Sweep"translation="تمشيط أمني"explanation="Contains a single, fast moving enemy that moves up and down."/>
<roomnamex="12"y="4"english="Gantry and Dolly"translation="الرافعة الجسرية"explanation="Gantry and Dolly are the names for types of cranes that move crates around. This room has two different types of platforms."/>
<roomnamex="12"y="5"english="The Yes Men"translation="عشاق كلمة نعم"explanation="Contains a number of enemies with briefcases and the word -YES- for a head. An expression for people who work at large companies and agree a lot with their bosses"/>
<roomnamex="12"y="6"english="Stop and Reflect"translation="وقفة تأمل"explanation="Expression meaning to take a moment and think about what you're doing. In this room, a small puzzle where you need to use the underside of a moving platform to progress."/>
<roomnamex="12"y="7"english="V Stitch"translation="غرزة مثلثة"explanation="A V Stitch is a type of crochet stitch."/>
<roomnamex="12"y="11"english="Upstream Downstream"translation="مع التيار، عكس التيار"explanation="As in swimming upstream or downstream, with or against a current in a river"/>
<roomnamex="12"y="12"english="The High Road is Low"translation="أعلى الطريقين مقاما أدناهما"explanation="This room has two paths - a high path and a low path. The 'low' path leads to a trinket, so the roomname is a sort of clue about which way to go."/>
<roomnamex="12"y="13"english="Give Me A V"translation="صفحة! سبعة! صعبة!"explanation="Room is in the shape of a big letter V. The roomname suggests the common american cheerleading chant - e.g. Give me an L! Give me an O! Give me a C! Give me an A! Give me an L! Give me an I! Give me an S! Give me an A! Give me a T! Give me an I! Give me an O! Give me an N! What does it spell? LOCALISATION!"/>
<roomnamex="12"y="14"english="Outer Hull"translation="المتن الخارجي"explanation="The entrance to the Space Station 2 level - the outer hull of a space station."/>
<roomnamex="13"y="0"english="It's Not Easy Being Green"translation="الخضرة صعبة"explanation="References a song by Kermit from the Muppets. This room is where you find the green crewmate."/>
<roomnamex="13"y="3"english="Linear Collider"translation="المصادم الخطي"explanation="An early room, name is just meant to suggest something sciency. Room contains long, wave like enemies."/>
<roomnamex="13"y="4"english="Comms Relay"translation="ناقل البث"explanation="This room contains some communication equipment, like a radio."/>
<roomnamex="13"y="5"english="Welcome Aboard"translation="مرحبا بك"explanation="The first room in the game"/>
<roomnamex="13"y="6"english="Trench Warfare"translation="حرب الخنادق"explanation="Room contains a couple of pits with soldier-like enemies in them. Loosely references the 1983 videogame Hunchback."/>
<roomnamex="13"y="7"english="B-B-B-Busted"translation="هههذه المصيدة حافلة"explanation="Room contains a large Bus. 'Bus'ted as in 'Caught'."/>
<roomnamex="13"y="8"english="Level Complete!"translation="ختمت المستوى!"explanation="This room has a teleporter, which is normally found at the end of a level. However this room is midway through the stage."/>
<roomnamex="13"y="9"english="Lighter Than Air"translation="أخف من الهواء"explanation="This room has clouds that rise from the bottom of the screen to the top, which the player is faster than, implying that the player is lighter than air."/>
<roomnamex="13"y="10"english="The Solution is Dilution"translation="الحل في الانحلال"explanation="This room has a factory and pollution clouds in it. Apparently this phrase was once used by industrialists to advocate for not worrying too much about pollution."/>
<roomnamex="13"y="11"english="The Cuckoo"translation="كوكو الكذوب"explanation="This room contains a speaker that emits the word 'LIES' over and over. A cuckoo's call decieves other birds!"/>
<roomnamex="13"y="12"english="Backsliders"translation="بساط الريح، بساط الرجع"explanation="A conveyor belt in this room pushes against you as you try to move, so you slide backwards."/>
<roomnamex="13"y="13"english="Select Track"translation="مساران ولك الخيار"explanation="There are two paths you can pick between here"/>
<roomnamex="14"y="0"english="Green Dudes Can't Flip"translation="الخضر لا يعكسون"explanation="You have a green crewmate with you in this room! A reference to the 90's film 'White Guy's Can't Jump'."/>
<roomnamex="14"y="1"english="This is how it is"translation="هكذا الحال"explanation="literally as in, this is how the mechanic of this stage works - also an expression as in 'this is the way things are'"/>
<roomnamex="14"y="2"english="That's Why I Have To Kill You"translation="لهذا سأضطر لقتلك"explanation="The follows the room named 'I love you'. 'I love you, that's why I have to kill you' is kind of a slasher horror trope."/>
<roomnamex="14"y="3"english="Atmospheric Filtering Unit"translation="وحدة المصفاة الجوية"explanation="An early room, looks a bit like an air filter"/>
<roomnamex="14"y="4"english="It's a Secret to Nobody"translation="سر مفضوح للجميع"explanation="A reference to the infamous Zelda quote 'It's a secret to everybody'. This room contains the first trinket."/>
<roomnamex="14"y="5"english="Conundrum"translation="حيرة"explanation="Conundrum as in puzzle, riddle, problem to be solved"/>
<roomnamex="14"y="6"english="Boo! Think Fast!"translation="بعو! بسرعة فلنفكر!"explanation="Contains a challenge that you need to react very quickly to. You might say 'Boo, think fast' if you threw something at someone, expecting them to catch it."/>
<roomnamex="14"y="7"english="The Sensible Room"translation="الغرفة العقلانية"explanation="Early corridor room containing no challenges. Sensible as in the opposite of Foolish - you might call someone sensible in english if they are excessively cautious."/>
<roomnamex="14"y="8"english="The Hanged Man, Reversed"translation="مشنوق، لكن بالعكس"explanation="Named after the Tarot Card, reversed as in Upside Down. The room contains a stationary enemy which resembles a Wheel of Fortune. The name is supposed to suggest a kind of out-of-place quality."/>
<roomnamex="14"y="9"english="Green Grotto"translation="المغارة الخضراء"explanation="A peaceful green room."/>
<roomnamex="14"y="10"english="Manic Mine"translation="المنجم المجنون"explanation="A reference to the 8-bit game Manic Miner."/>
<roomnamex="14"y="11"english="Clarion Call"translation="نداء الكاذب"explanation="A 'Clarion Call' is an idiom used when somebody makes a case for a course of action, for example in a politician's speech, or a call to battle. It sometimes has an association with dishonesty - in this room, the words 'LIES' appear over and over."/>
<roomnamex="14"y="12"english="Gordian Knot"translation="المعقودة الغوردية"explanation="As in the Gordian Knot from greek history. A complicated room that can be passed through twice."/>
<roomnamex="14"y="13"english="You Chose... Poorly"translation="اختيارك... لم يكن في محله"explanation="This room comes right after a choice between two paths. It's a quote from an Indiana Jones film."/>
<roomnamex="15"y="0"english="Murdering Twinmaker"translation="آلة التوأمة الذباحة"explanation="Room contains a teleporter. A 'Murdering Twinmaker' is, uh, one way teleportation might work..."/>
<roomnamex="15"y="1"english="A Bisected Spiral"translation="دوامة مشطورة"explanation="Room is a spiral, cut down the middle"/>
<roomnamex="15"y="2"english="Take the Red Pill"translation="عليك بالحبة الحمراء"explanation="This is a Matrix reference that hasn't aged well, lol"/>
<roomnamex="15"y="3"english="Traffic Jam"translation="زحمة مرور"explanation="The enemies in this room are Stop Signs"/>
<roomnamex="15"y="4"english="Leap of Faith"translation="نتوكل ونقفز"explanation="To take a leap of faith means to do something without knowing how it's going to turn out."/>
<roomnamex="15"y="5"english="Solitude"translation="عزلة"explanation="As in being alone, or in this case, lost by yourself"/>
<roomnamex="15"y="6"english="Driller"translation="الكابتن حفار"explanation="Technically references the name of a C64 game, but that doesn't matter much"/>
<roomnamex="15"y="7"english="Exhaust Chute"translation="مسقط النفايات"explanation="Like a factory exhaust chute for disposing of rubbish"/>
<roomnamex="15"y="8"english="Sorrow"translation="يا للأحزان"explanation="A difficult room that you might die in a lot"/>
<roomnamex="15"y="9"english="doomS"translation="قبر حرب في rbb nlSo"explanation="The room is above 'Swoop', and is a copy of the room rotated 180 degrees! The room name 'doomS' is the word 'Swoop' rotated 180 degrees. When localising this room, don't worry too much about trying to keep the meaning of the words Dooms and Swoop, because they're not that important - instead, focus on picking words that have this 180 degree flip quality!"/>
<roomnamex="15"y="10"english="Swoop"translation="nrc rib في مكان قفر"explanation="See the note for room (15,9), doomS."/>
<roomnamex="15"y="11"english="Chinese Rooms"translation="الغرف الصينية"explanation="This refers to a famous philosophical argument about artifical intelligence - https://en.wikipedia.org/wiki/Chinese_room."/>
<roomnamex="15"y="12"english="You Just Keep Coming Back"translation="ستغيب، ستروح، وسترجع يا حياتي"explanation="You can pass through this room up to three times, depending on which route you take through the level."/>
<roomnamex="15"y="13"english="Hyperspace Bypass 5"translation="متخطى الفضاء الفائق رقم 5"explanation="A conveyor belt will take you through this room without you needing to press any buttons, hench the bypass. The phrase Hyperspace Bypass is a reference to Hitchhiker's Guide to the Galaxy."/>
<roomnamex="16"y="0"english="I Love You"translation="أحبك"explanation="The room contains a couple of heart shaped enemies"/>
<roomnamex="16"y="1"english="As you like it"translation="كما شئت"explanation="This room can be approached in two different equivilent ways, whichever way you like it. 'As you like it' is the name of a Shakespeare play."/>
<roomnamex="16"y="2"english="Short Circuit"translation="دارة مقصورة"explanation="Probably named after the 80s film Short Circuit. Also works because you'll hit a dead end if you keep walking forwards."/>
<roomnamex="16"y="3"english="Twisty Little Passages"translation="طرق تقلصت وتشعبت"explanation="A maze like room. Refers to the section from the 1976 text game Colossal Cave Adventure - you are in a maze of twisty little passages, all alike"/>
<roomnamex="16"y="6"english="Quicksand"translation="الرمال المتحركة"explanation="Contains lots of dissolving platforms."/>
<roomnamex="16"y="7"english="The Tomb of Mad Carew"translation="قبر كارو المجنون"explanation="A very obscure reference to the C64 game Dizzy"/>
<roomnamex="16"y="8"english="Parabolica"translation="العدسة الشلجمية"explanation="This room contains a section of wall in the shape of a parabolic arch."/>
<roomnamex="16"y="9"english="$eeing Dollar $ign$"translation="أرى ﺷ$ﻞ دولار"explanation="This is a green room that resembles a dollar sign shape"/>
<roomnamex="16"y="10"english="What Lies Beneath?"translation="تحت الأكاذيب؟"explanation="The room below this contains enemies in the shape of the word 'Lies'. So there's a double meaning here - as in, a question, 'what is below this room', and that the word LIES is literally beneath this room."/>
<roomnamex="16"y="11"english="Spikes Do!"translation="تحصد الأشواك!"explanation="This rooms is below the room named 'What lies Beneath?', and answers the question: Spikes do!"/>
<roomnamex="16"y="12"english="Ha Ha Ha Not Really"translation="هههههه صدقتني"explanation="This is a difficult room that follows one called 'Plain Sailing from here on'. It's taunting the player. "/>
<roomnamex="16"y="13"english="Plain Sailing from Here On"translation="طريق سالكة من الآن فصاعدا"explanation="This room is at the end of a long section, and promises 'Plain Sailing' afterwards, as in, no further challenges. This is a lie"/>
<roomnamex="17"y="0"english="As we go up, we go down"translation="صعودنا سقوط"explanation="Named after the 1995 song by Guided by Voices"/>
<roomnamex="17"y="1"english="Maze With No Entrance"translation="متاهة بلا مدخل"explanation="This room is a maze which has no entrance, due to the nature of the warping mechanic."/>
<roomnamex="17"y="2"english="The Brown Gate"translation="البوابة البنية"explanation="I think this is an Ultima 7 reference? A literal translation is fine here"/>
<roomnamex="17"y="3"english="Edge Games"translation="نلعب على حافة الخطر"explanation="Contains a trinket that you get by navigating around the edge of the screen. The use of the word EDGE is deliberate, refering to the trademark of a notoriously litigious individual who sued an indie developer around the time VVVVVV was made"/>
<roomnamex="17"y="7"english="Brass Sent Us Under The Top"translation="كتيبة المتسلقين"explanation="The enemies in this room look like little army guys"/>
<roomnamex="17"y="8"english="The Warning"translation="من أنذر فقد أعذر"explanation="This room has lots of checkpoints in it. It's beside the game's most difficult challenge, the Veni, Vidi, Vici section. The checkpoints don't really do anything, but they're a warning of the challenge ahead."/>
<roomnamex="17"y="9"english="Just Pick Yourself Down"translation="التقط نفسك من تحت"explanation="A two part room name. From the expression 'If you fall down, just pick yourself up'."/>
<roomnamex="17"y="10"english="If You Fall Up"translation="لو سقطت إلى فوق"explanation="A two part room name. From the expression 'If you fall down, just pick yourself up'."/>
<roomnamex="17"y="11"english="Chipper Cipher"translation="شفرة بهجة"explanation="Just some nice wordplay. Chipper means 'Jolly' or 'Happy'."/>
<roomnamex="18"y="0"english="Time to get serious"translation="حان وقت الجد"explanation="Literal, this is the first room in the stage which is fairly difficult"/>
<roomnamex="18"y="1"english="Wheeler's Wormhole"translation="الثقب الدودي، حسب ويلر"explanation="Apparently a scientist named John Wheeler coined the phrase 'Wormhole'! I just found that out. Anyway, this room has a teleporter in it."/>
<roomnamex="18"y="2"english="Sweeney's Maze"translation="متاهة سويني"explanation="Contains enemies that move strangly and resemble enemies from ZZT, an old game by Tim Sweeney"/>
<roomnamex="18"y="3"english="Mind The Gap"translation="حذار من الفجوة"explanation="Refers to what train announcers say on the London Underground when you leave the train - mind the gap between the train and the station platform"/>
<roomnamex="18"y="7"english="A Wrinkle in Time"translation="بين طيات الزمان"explanation="The name of a 60's science fiction book. There is a teleporter in this room."/>
<roomnamex="18"y="8"english="Getting Here is Half the Fun"translation="الوصول نصف المتعة"explanation="The top of the Veni Vidi Vici sequence. When you get to the top, you have to go all the way back down - hence, this room is the halfway point of the challenge."/>
<roomnamex="18"y="9"english="Your Bitter Tears... Delicious"translation="بكيت بمرارة... دموعك حلاوة"explanation="Part of the Veni Vidi Vici sequence. Taunting the player."/>
<roomnamex="18"y="10"english="Easy Mode Unlocked"translation="فتحت الطور السهل"explanation="Part of the Veni Vidi Vici sequence. Taunting the player - the 'easy mode' refers to the second passageway on the right side that is easier to take when going back through this room on the way down, but that passageway leads to death."/>
<roomnamex="18"y="11"english="Vici!"translation="جئت!"explanation="The rooms Veni, Vidi, Vici! appear in sequence. Famous latin phrase attributed to Julius Caesar meaning I came, I saw, I conquered. The hardest challenge in the game."/>
<roomnamex="18"y="12"english="Vidi"translation="نظرت!"explanation="The rooms Veni, Vidi, Vici! appear in sequence. Famous latin phrase attributed to Julius Caesar meaning I came, I saw, I conquered. The hardest challenge in the game."/>
<roomnamex="18"y="13"english="Veni"translation="فزت!"explanation="The rooms Veni, Vidi, Vici! appear in sequence. Famous latin phrase attributed to Julius Caesar meaning I came, I saw, I conquered. The hardest challenge in the game."/>
<roomnamex="18"y="14"english="Doing Things The Hard Way"translation="صعبت الطريق على نفسك"explanation="The starting room from the Veni, Vidi, Vici! challenge."/>
<roomnamex="19"y="0"english="To The Batcave!"translation="إلى مغارة الوطواط!"explanation="The Batcave, as in Batman's hideout."/>
<roomnamex="19"y="1"english="Ascending and Descending"translation="صواعد ونوازل"explanation="Just meant as in literally Ascending and Descending, going up and down. There is a room later in the game called Upstairs, Downstairs, which is a callback to this."/>
<roomnamex="19"y="2"english="Shockwave Rider"translation="راكب الموجات"explanation="Named after a 70's Science Fiction novel"/>
<roomnamex="19"y="3"english="This will make you flip"translation="سيقلب هذا مزاجك"explanation="Flip is used here in the sense 'Flip out', as in, to lose your temper"/>
<roomnamex="41"y="51"english="1950 Silverstone Grand V"translation="كأvv سلفرستون 1950"explanation="Refers to 1950 Silverstone Grand Prix (The final stage has room names that suggest old black and white TV shows.)"/>
<roomnamex="41"y="52"english="DIY V Repair"translation="تصليح V بدون معلم"explanation="DIY TV repair - this room has a television you can interact with, which changes the theme from black and white to colour"/>
<roomnamex="41"y="56"english="Now Take My Lead"translation="والآن ستمشي ورائي"explanation="This entire intermission section has a tone of a strict schoolteacher leading a small child."/>
<roomnamex="42"y="52"english="Party Time!"translation="حفلة بلا ميعاد!"explanation="The first room after the black and white section. Doesn't refer to any TV show."/>
<roomnamex="42"y="56"english="What Are You Waiting For?"translation="ماذا تنتظر؟"explanation="This entire intermission section has a tone of a strict schoolteacher leading a small child."/>
<roomnamex="43"y="51"english="The Voon Show"translation="برنامج القلاﺑvv"explanation="Named after The Goon Show (The final stage has room names that suggest old black and white TV shows.)"/>
<roomnamex="43"y="52"english="Upstairs, Downstairs"translation="طالع، نازل"explanation="Named after the 70s TV show - but also, refers to the earlier room Ascending and Descending, which this room is an updated version of. (Second part of the final stage has references to Colour TV shows)"/>
<roomnamex="43"y="56"english="Don't Get Ahead of Yourself!"translation="إياك واستباق الأمور!"explanation="This entire intermission section has a tone of a strict schoolteacher leading a small child."/>
<roomnamex="44"y="51"english="Vertigo"translation="٧وار المرتفعات"explanation="Named after the Hitchcock film (The final stage has room names that suggest old black and white TV shows.)"/>
<roomnamex="44"y="52"english="Timeslip"translation="مخطوف الزمان"explanation="Named after the 70s TV show - name also suggests a connection to the earlier room Backsliders, which this room is an updated version of (Second part of the final stage has references to Colour TV shows)"/>
<roomnamex="44"y="56"english="Very Good"translation="حسن جدا"explanation="This entire intermission section has a tone of a strict schoolteacher leading a small child."/>
<roomnamex="45"y="52"english="Three's Company"translation="سر الثلاثة في حفظ الأمناء"explanation="Named after the 70s sitcom - but also, suggests a connection to the earlier room Two's Company, which this room is an updated version of (Second part of the final stage has references to Colour TV shows)"/>
<roomnamex="45"y="56"english="Must I Do Everything For You?"translation="هل أساعدك في الصغيرة والكبيرة؟"explanation="This entire intermission section has a tone of a strict schoolteacher leading a small child."/>
<roomnamex="46"y="54"english="Temporary Fault..."translation="عطل مؤقت..."explanation="Opening room of the final level."/>
<roomnamex="46"y="56"english="Now Stay Close To Me..."translation="فلنمش قريبين من بعض..."explanation="This entire intermission section has a tone of a strict schoolteacher leading a small child."/>
<roomnamex="47"y="52"english="Cosmic Creepers"translation="المتلصص الفضائي"explanation="Named after the cat from the 70s film Bedknobs and Broomsticks. Not sure why!"/>
<roomnamex="47"y="54"english="Do Not Adjust the V-hold"translation="لا حاجة لتحريك الهوائي ٧"explanation="I don't think V-hold is a real thing, it's supposed to just suggest 'Do not adjust your TV settings'. (The final stage has room names that suggest old black and white TV shows.)"/>
<roomnamex="47"y="56"english="...But Not Too Close"translation="...لكن بدون قرب زائد"explanation="In this room, if you go too quickly, your crewmate will walk into spikes."/>
<roomnamex="48"y="52"english="The Villi People"translation="معي غليظ"explanation="Bennett just thought this room looked 'intestinal'. Villi as in part of the intestinal system. Also refers to the 80s band The Village People."/>
<roomnamex="48"y="54"english="Regular Service Will Return Shortly"translation="سنعود بعد قليل"explanation="This is something you might hear on a TV station if they had lost reception. (The final stage has room names that suggest old black and white TV shows. A really, really good way to translate this level would be to use the names of black and white TV shows that are well known in your language, rather than trying to keep the exact meaning of the TV shows used here.)"/>
<roomnamex="48"y="56"english="Don't Be Afraid"translation="لا داع للخوف"explanation="This entire intermission section has a tone of a strict schoolteacher leading a small child."/>
<roomnamex="49"y="52"english="Panic Room"translation="غرفة الذعر"explanation="A panic room is a safe room that you can hide in during an emergency, but I don't think that was Bennett's intention with this name. This room suddenly starts scrolling as soon as you enter, causing a panic."/>
<roomnamex="49"y="54"english="Origami Room"translation="غرفة في طي النسيان"explanation="As in folded paper - this room is mirrored around the center point."/>
<roomnamex="49"y="56"english="Do as I Say..."translation="طالما سمعت كلامي..."explanation="This entire intermission section has a tone of a strict schoolteacher leading a small child."/>
<roomnamex="50"y="51"english="The V Stooges"translation="البطل الخماسي"explanation="Refers to the 3 Stooges (The final stage has room names that suggest old black and white TV shows.)"/>
<roomnamex="50"y="52"english="1954 World Cup Vinyl"translation="فينيل كأvv العالم 1954"explanation="Refers to the World Cup Final (The final stage has room names that suggest old black and white TV shows.)"/>
<roomnamex="50"y="56"english="...Not as I Do"translation="...بدل تقليد أفعالي"explanation="This entire intermission section has a tone of a strict schoolteacher leading a small child."/>
<roomnamex="51"y="53"english="The Final Challenge"translation="التحدي النهائي"explanation="One of the last challenges in the game."/>
<roomnamex="51"y="56"english="Mind Your Head"translation="حذار، رأسك والسقف"explanation="This entire intermission section has a tone of a strict schoolteacher leading a small child."/>
<roomnamex="52"y="53"english="The Last Straw"translation="القشة التي قصمت"explanation="One more little challenge, just after the room called The Final Challenge"/>
<roomnamex="52"y="56"english="Do Try To Keep Up"translation="هلا لحقتني، بدون تباطئ؟"explanation="This entire intermission section has a tone of a strict schoolteacher leading a small child."/>
<roomnamex="53"y="48"english="Whee Sports"translation="`v´ ويهي ما أحلى الرياضة `v´"explanation="Refers to Wii Sports, the Nintendo Wii launch title. 'Whee' as in what a child might say while going down a slide (this room has a long drop in it)"/>
<roomnamex="53"y="49"english="Whizz Down The Shaft"translation="الصعوv إلى الهاوية"explanation="Whizz is Australian slang for doing something quickly, I think"/>
<roomnamex="53"y="50"english="The Gravitron"translation="أتون الجاذبية"explanation="A special arcade section where you have to survive for 60 seconds. Bennett named this one so as to suggest a funfair ride (though not any one in particular)."/>
<roomnamex="53"y="51"english="Tunnel of Terror"translation="أنفاق الأهوال"explanation="Another name inspired by funfairs."/>
<roomnamex="53"y="52"english="House of Mirrors"translation="متاهة المرايا"explanation="Another name inspired by funfairs."/>
<roomnamex="53"y="53"english="W"translation="W"explanation="This room has platforms in a W shape."/>
<roomnamex="53"y="56"english="You're Falling Behind"translation="تأخرت في النزول!"explanation="This entire intermission section has a tone of a strict schoolteacher leading a small child."/>
<roomnamex="54"y="48"english="VVVVVV"translation="VVVVVV"explanation="Final sequence of rooms that spell out V-V-V-V-V-V, 6/6"/>
<roomnamex="54"y="49"english="VVVVV"translation="VVVVV"explanation="Final sequence of rooms that spell out V-V-V-V-V-V, 5/6"/>
<roomnamex="54"y="50"english="VVVV"translation="VVVV"explanation="Final sequence of rooms that spell out V-V-V-V-V-V, 4/6"/>
<roomnamex="54"y="51"english="VVV"translation="VVV"explanation="Final sequence of rooms that spell out V-V-V-V-V-V, 3/6"/>
<roomnamex="54"y="52"english="VV"translation="VV"explanation="Final sequence of rooms that spell out V-V-V-V-V-V, 2/6"/>
<roomnamex="54"y="53"english="V"translation="V"explanation="Final sequence of rooms that spell out V-V-V-V-V-V, 1/6"/>
<roomnamex="54"y="56"english="Class Dismissed!"translation="انتهى الدرس!"explanation="This entire intermission section has a tone of a strict schoolteacher leading a small child."/>
<roomnameenglish="The Super Gravitron"translation="أتون الجاذبية الخارق"explanation="An expanded version of the room at 53, 50"/>
<roomnameenglish="I Can't Believe You Got This Far"translation="لا أصدق وصولك هذا الحد"explanation="If you're playing No Death Mode, the room Prize for the Reckless has this roomname instead (the room is altered to make it possible to do this section without dying)"/>
<roomnameenglish="Imagine Spikes There, if You Like"translation="يمكنك تخيل أشواك هناك لو شئت"explanation="If you're playing a time trial, the room Prize for the Reckless has this roomname instead (the room is altered to make it possible to do this section without dying)"/>
<!----->
<roomnameenglish="Rear Window"translation="النافذة الخلفية"explanation="Named after the Hitchcock film (The final stage has room names that suggest old black and white TV shows.)"/>
<roomnameenglish="On the Waterfront"translation="على جبهة البحر"explanation="Named after the 1954 film (The final stage has room names that suggest old black and white TV shows.)"/>
<roomnameenglish="On the Vaterfront"translation="على ٧بهة البحر"explanation=""/>
<!----->
<roomnameenglish="The Untouchables"translation="أناس فوق مستوى النقد"explanation="Before it was a film, the Untouchables was a TV series in 1959 (The final stage has room names that suggest old black and white TV shows.)"/>
<roomnameenglish="The Untouchavles"translation="أناس فوق مستوى ﺍﻟﻨﻘ٧"explanation=""/>
<!----->
<roomnameenglish="Television Newsveel"translation="نشرة الأنباء"explanation="Refers to Television Newsreel. (The final stage has room names that suggest old black and white TV shows.)"/>
<roomnameenglish="The 9 O'Clock News"translation="أخبار التاسعة"explanation="(Second part of the final stage has references to Colour TV shows)"/>
<!----->
<roomnameenglish="Vwitched"translation="المسحورة"explanation="Reference to early black and white sitcom Bewitched (The final stage has room names that suggest old black and white TV shows.)"/>
<roomnameenglish="Diav M for Mdrver"translation="الجريمة شبه الكاملة"explanation=""/>
<roomnameenglish="Dial M for Murder"translation="الجري[م]ة شبه الكاملة"explanation="Named after the Hitchcock film (Second part of the final stage has references to Colour TV shows)"/>
<!----->
<roomnameenglish="Gvnsmoke"translation="ويسترن"explanation="Gunsmoke was a black and white Western (The final stage has room names that suggest old black and white TV shows.)"/>
<roomnameenglish="Gunsmoke 1966"translation="غانسموك 1966"explanation="Gunsmoke changed to colour in 1966 (Second part of the final stage has references to Colour TV shows)"/>
<!----->
<roomnameenglish="Please enjoy these repeats"translation="استمتعوا بالإعادة"explanation="This stage also has a number of rooms which are harder versions of easier challenges, like this one. (The final stage has room names that suggest old black and white TV shows.)"/>
<roomnameenglish="In the Margins"translation="على الهوامش"explanation="Not sure if this has a TV show reference, might just be meant literally (Second part of the final stage has references to Colour TV shows)"/>
<!----->
<roomnameenglish="Try Jiggling the Antenna"translation="فلنجرب خرخشة الهوائي"explanation="(The final stage has room names that suggest old black and white TV shows.)"/>
<roomnameenglish="Try Viggling the Antenna"translation="ڤلثچژپ څژڅشڤ الهڤائي"explanation=""/>
<roomnameenglish="Veavvn's Gvte"translation="أپڤاپ الچثڤ"explanation="Named after the 1980's film (Second part of the final stage has references to Colour TV shows)"/>
<roomnameenglish="Heaven's Gate"translation="أبواب الجنة"explanation="Named after the 1980's film (Second part of the final stage has references to Colour TV shows)"/>
<!-- Please read README.txt for information about the language files -->
<stringsmax_local_for="8x10">
<stringenglish="LOADING... {percent|digits=2|spaces}%"translation="جار التحميل...{percent|digits=2|spaces}%"explanation="on loading screen. {percent} = number from 0-100"max="26"max_local="26"/>
<stringenglish="continue from teleporter"translation="مواصلة من التنقيلة"explanation="menu option, game save that was made at a teleporter"/>
<stringenglish="Tele Save"translation="تخزينة تنقيلة"explanation="title, game save that was made at a teleporter"max="20"max_local="20"/>
<stringenglish="continue from quicksave"translation="مواصلة من التخزينة السريعة"explanation="menu option, game save that was made freely from the menu (at any checkpoint)"/>
<stringenglish="Quick Save"translation="تخزينة سريعة"explanation="title, game save that was made freely from the menu (at any checkpoint)"max="20"max_local="20"/>
<stringenglish="unlock play modes"translation="فتح أطوار اللعب"explanation="menu option"/>
<stringenglish="Unlock Play Modes"translation="فتح أطوار اللعب"explanation="title"max="20"max_local="20"/>
<stringenglish="Unlock parts of the game normally unlocked as you progress."translation="فتح أطوار اللعب فتحا عاديا حسب تقدمك في اللعبة."explanation=""max="38*5"max_local="38*5"/>
<stringenglish="From here, you may unlock parts of the game that are normally unlocked as you play."translation="يمكنك من هنا فتح أجزاء من اللعبة فورا، كان من المفروض أن تفتح تدريجيا أثناء اللعب العادي."explanation=""max="38*5"max_local="38*5"/>
<stringenglish="Change the language."translation="تغيير اللغة."explanation=""max="38*2"max_local="38*2"/>
<stringenglish="Can not change the language while a textbox is displayed in-game."translation="لا يمكن تغيير اللغة أثناء ظهور نص في اللعبة."explanation=""max="38*3"max_local="38*3"/>
<stringenglish="clear main game data"translation="حذف بيانات اللعبة الأساسية"explanation="menu option"/>
<stringenglish="clear custom level data"translation="حذف بيانات المراحل الاختيارية"explanation="menu option"/>
<stringenglish="Delete your main game save data and unlocked play modes."translation="حذف بيانات حفظ اللعبة الأساسية وما يخص أطوار اللعب المفتوحة."explanation=""max="38*5"max_local="38*5"/>
<stringenglish="Delete your custom level save data and completion stars."translation="حذف بيانات المراحل الاختيارية وما يخص نجوم الإكمال."explanation="completion stars: when completing a level you get either 1 or 2 stars"max="38*5"max_local="38*5"/>
<stringenglish="Are you sure? This will delete your current saves..."translation="أحقاقررتذلك؟
<stringenglish="Are you sure you want to delete all your saved data?"translation="هل تأكدت من قرارك بحذف كل بياناتك المحفوظة؟"explanation=""max="38*7"max_local="38*7"/>
<stringenglish="Are you sure you want to delete your quicksave?"translation="هل تأكدت من قرارك بحذف تخزينتك السريعة؟"explanation="only the quicksave of a custom level"max="38*7"max_local="38*7"/>
<stringenglish="no! don't delete"translation="لا! دعها ولا تحذفها"explanation="menu option"/>
<stringenglish="yes, delete everything"translation="أجل، احذف كل شيء"explanation="menu option"/>
<stringenglish="Choose letterbox/stretch/integer mode."translation="طريقة تكبير الصورة لتملأ الشاشة."explanation="See the `Current mode` explanations for more details on the modes"max="38*3"max_local="38*3"/>
<stringenglish="Current mode: INTEGER"translation="الوضع الحالي: مضاعف صحيح"explanation="integer (whole number) mode only enlarges the game in exact multiples of 320x240"max="38*2"max_local="38*2"/>
<stringenglish="Current mode: STRETCH"translation="الوضع الحالي: تمطيط الصورة"explanation="stretch mode just stretches the game to fill the window content"max="38*2"max_local="38*2"/>
<stringenglish="Current mode: LETTERBOX"translation="الوضع الحالي: حواف سوداء"explanation="letterbox mode enlarges the game to the window, but adds black bars to make the aspect ratio 4:3"max="38*2"max_local="38*2"/>
<stringenglish="resize to nearest"translation="التكبير لأقرب مضاعف"explanation="menu option. The game will be resized to the closest multiple of the normal resolution, 320x240, so for example if your window is 682x493, it will resize to 640x480"/>
<stringenglish="Resize to Nearest"translation="تكبير لأقرب مضاعف"explanation="title. The game will be resized to the closest multiple of the normal resolution, 320x240, so for example if your window is 682x493, it will resize to 640x480"max="20"max_local="20"/>
<stringenglish="Resize to the nearest window size that is of an integer multiple."translation="تكبير لأقرب حجم شاشة يكون عددا مضاعفا صحيحا للأبعاد الأصلية."explanation="The game will be resized to the closest multiple of the normal resolution, 320x240, so for example if your window is 682x493, it will resize to 640x480"max="38*3"max_local="38*3"/>
<stringenglish="You must be in windowed mode to use this option."translation="يشترط هذا الخيار وضع النافذة."explanation="The game cannot be in fullscreen to resize the window"max="38*2"max_local="38*2"/>
<stringenglish="Current mode: NEAREST"translation="الوضع الحالي: أقرب جار"explanation="nearest neighbor filter"max="38*2"max_local="38*2"/>
<stringenglish="toggle analogue"translation="تفعيل صورة البث التناظري"explanation="menu option, analogue mode simulates distortion from bad signal"/>
<stringenglish="Analogue Mode"translation="صورة البث التناظري"explanation="title, analogue mode simulates distortion from bad signal"max="20"max_local="20"/>
<stringenglish="There is nothing wrong with your television set. Do not attempt to adjust the picture."translation="تلفازك بخير. لا تحاول تصحيح صورته."explanation=""max="38*5"max_local="38*5"/>
<stringenglish="toggle fps"translation="تفعيل حد الإطارات"explanation="menu option, kind of a misnomer, toggle between 30 FPS and more than 30 FPS"/>
<stringenglish="With Music by"translation="موسيقاها من"explanation="credits"max="30"max_local="30"/>
<stringenglish="Rooms Named by"translation="أسماء غرفها من"explanation="credits"max="30"max_local="30"/>
<stringenglish="C++ Port by"translation="نقلها إلى ++C من"explanation="credits"max="30"max_local="30"/>
<stringenglish="Patrons"translation="داعموها"explanation="credits, people who donated a certain amount before the game originally released"max="13"max_local="13"/>
<stringenglish="VVVVVV is supported by the following patrons"translation="لعبة VVVVVV يدعمها المتبرعون المذكورون"explanation="credits"max="38*3"max_local="38*3"/>
<stringenglish="and also by"translation="علاوة على"explanation="credits, VVVVVV is also supported by the following people"max="38*2"max_local="38*2"/>
<stringenglish="and"translation="ومعهم"explanation="credits. This list of names, AND furthermore, this other list of names"max="38"max_local="38"/>
<stringenglish="GitHub Contributors"translation="مساهمو غيتهب"explanation="credits. This doesn't _really_ need `GitHub` specifically, it could be replaced with `Code`"max="20"max_local="20"/>
<stringenglish="With contributions on GitHub from"translation="مع مساهمات برمجة غيتهب من"explanation="credits"max="40"max_local="40"/>
<stringenglish="and thanks also to:"translation="مع شكر خاص كذلك:"explanation="credits, and thanks also to ... you!"max="38*3"max_local="38*3"/>
<stringenglish="You!"translation="لك أنت!"explanation="credits, and thanks also to ... you!"max="20"max_local="20"/>
<stringenglish="Your support makes it possible for me to continue making the games I want to make, now and into the future."translation="دعمكم يمكنني من الاستمرار في صنع الألعاب التي أريدها اليوم ومستقبلا."explanation="credits"max="38*5"max_local="38*5"/>
<stringenglish="You cannot save in this mode."translation="لا يمكنك الحفظ في هذا الطور."explanation=""max="38*3"max_local="38*3"/>
<stringenglish="Would you like to disable the cutscenes during the game?"translation="هل تأكدت من قرارك بتعطيل المشاهد أثناء اللعبة؟"explanation=""max="38*6"max_local="38*6"/>
<stringenglish="Flip is bound to: "translation="العكس معين للزر: "explanation="controller binds, bound to A, B, X, Y, etc. These strings end with a space!"max="32"max_local="32"/>
<stringenglish="Enter is bound to: "translation="الدخول معين للزر: "explanation="controller binds, bound to A, B, X, Y, etc. These strings end with a space!"max="32"max_local="32"/>
<stringenglish="Menu is bound to: "translation="القائمة معينة للزر: "explanation="controller binds, bound to A, B, X, Y, etc. These strings end with a space!"max="32"max_local="32"/>
<stringenglish="Restart is bound to: "translation="إعادة الغرفة معينة للزر: "explanation="in-game death key to restart at checkpoint. Controller binds, bound to A, B, X, Y, etc. These strings end with a space!"max="32"max_local="32"/>
<stringenglish="Interact is bound to: "translation="التفاعل معين للزر: "explanation="controller binds, bound to A, B, X, Y, etc. These strings end with a space!"max="32"max_local="32"/>
<stringenglish="Press a button...|(or press ↑↓)"translation="يرجى ضغط زر...|(أو ضغط ↑↓)"explanation="the arrows represent up/down buttons, or stick movement... So: press a controller button, or navigate away"max="38*2"max_local="38*2"/>
<stringenglish="Add {button}?|Press again to confirm"translation="إضافة {button}؟|اضغط ثانية للتأكيد"explanation="Bind the X button to this action? Press X again to really add it"max="38*2"max_local="38*2"/>
<stringenglish="Remove {button}?|Press again to confirm"translation="إزالة {button}؟|اضغط ثانية للتأكيد"explanation="Remove the binding of the X button for this action? Press X again to really remove it"max="38*2"max_local="38*2"/>
<stringenglish="Interact is currently Enter!|See speedrunner options."translation="زر التفاعل حاليا Enter !|راجع إعدادات التختيم السريع."explanation="the Interact action can't be configured now because it's the same as the Enter action. There's an option in the Speedrunner options to split it off"max="38*2"max_local="38*2"/>
<stringenglish="ERROR: No language files found."translation="خطأ: لم نجد ملفات اللغة."explanation=""max="38*3"max_local="38*3"/>
<stringenglish="Repository language folder:"translation="مجلد اللغة في غيتهب: "explanation="Language folder from the Git repository"max="39"max_local="39"/>
<stringenglish="Some options that are useful for translators and developers."translation="بعض الاختيارات التي قد تفيد المترجمين والمطورين"explanation=""max="38*6"max_local="38*6"/>
<stringenglish="maintenance"translation="صيانة"explanation="menu option, menu that allows you to apply maintenance to translations"/>
<stringenglish="open lang folder"translation="فتح مجلد اللغة"explanation="menu option. Button that opens the folder with language files (which is called lang) in a file explorer"/>
<stringenglish="Sync all language files after adding new strings."translation="مزامنة كل ملفات اللغة بعد إضافة سطور جديدة."explanation=""max="38*6"max_local="38*6"/>
<stringenglish="translate room names"translation="ترجمة أسماء الغرف"explanation="menu option"/>
<stringenglish="Enable room name translation mode, so you can translate room names in context. Press I for invincibility."translation="تفعيل وضع ترجمة أسماء الغرف، حتى تترجمها في سياقها. اضغط I للحصانة."explanation=""max="38*4"max_local="38*4"/>
<stringenglish="You have not enabled room name translation mode!"translation="لم يفعل وضع ترجمة أسماء الغرف."explanation=""max="38*4"max_local="38*4"/>
<stringenglish="Cycle through most menus in the game. The menus will not actually work, all options take you to the next menu instead. Press Escape to stop."translation="يتفحص معظم قوائم اللعبة. لا تعمل عملها الأصلي، بل تنقلك للقائمة المختبرة الأخرى. اضغط ESC للتوقف."explanation=""max="38*6"max_local="38*6"/>
<stringenglish="Display all text boxes from cutscenes.xml. Only tests the basic appearance of each individual text box."translation="إظهار كل صناديق النصوص من cutscenes.xml لكنه لا يختبر إلا المظهر العام للنص دون سواه."explanation=""max="38*6"max_local="38*6"/>
<stringenglish="from clipboard"translation="من ذاكرة النسخ"explanation="menu option, paste script name from clipboard"/>
<stringenglish="Explore the rooms of any level in the game, to find all room names to translate."translation="استطلاع غرف أي مستوى من اللعبة لكشف كل أسماء الغرف التي تحتاج ترجمات."explanation=""max="38*6"max_local="38*6"/>
<stringenglish="global limits check"translation="تحقق الحد الشامل"explanation="menu option"/>
<stringenglish="Limits check"translation="التحقق من الحدود"explanation="title"max="20"max_local="20"/>
<stringenglish="Find translations that don't fit within their defined bounds."translation="البحث عن ترجمات جاوزت حدودها."explanation=""max="38*6"max_local="38*6"/>
<stringenglish="No text overflows found!"translation="لم نجد فيضان نصوص!"explanation="limits check. no strings go outside their max bounds"max="38*6"max_local="38*6"/>
<stringenglish="No text overflows left!"translation="لم نبق على فيضان نصوص!"explanation="limits check. we have seen all strings that go outside their max bounds"max="38*6"max_local="38*6"/>
<stringenglish="Note that this detection isn't perfect."translation="مع الملاحظة أن هذا التحقق ليس متقنا."explanation="limits check"max="38*6"max_local="38*6"/>
<stringenglish="sync language files"translation="مزامنة ملفات اللغة"explanation="menu option"/>
<stringenglish="Sync language files"translation="مزامنة ملفات اللغة"explanation="title, translation maintenance menu"max="20"max_local="20"/>
<stringenglish="Merge all new strings from the template files into the translation files, keeping existing translations."translation="توحيد كل السطور الجديدة من ملفات التمبليت إلى ملفات الترجمة بالحفاظ على السطور الكاملة السابقة."explanation="translation maintenance menu"max="38*6"max_local="38*6"/>
<stringenglish="Count the amount of untranslated strings for this language."translation="عدد السطور التي لم تترجم لهذه اللغة"explanation=""max="38*6"max_local="38*6"/>
<stringenglish="Count the amount of untranslated strings for each language."translation="عدد السطور التي لم تترجم لكل لغة"explanation="translation maintenance menu"max="38*6"max_local="38*6"/>
<stringenglish="If new strings were added to the English template language files, this feature will insert them in the translation files for all languages. Make a backup, just in case."translation="لو أضيفت سطور إنكليزية، تضيفها هذه الميزة لنصوص اللغات الأخرى. احتياطا، احتفظ بنسخة سلفا."explanation="translation maintenance menu"max="38*7"max_local="38*7"/>
<stringenglish="Full syncing EN→All:"translation="مزامنة شاملة من الانكليزية للكل:"explanation="translation maintenance menu. The following list of language files can be fully synced from English to all other languages by pressing this button - new strings will be inserted in all languages"max="40"max_local="40"/>
<stringenglish="Syncing not supported:"translation="المزامنة غير مدعومة:"explanation="translation maintenance menu. The following list of language files are untouched by the sync button"max="40"max_local="40"/>
<stringenglish="All other gameplay settings."translation="كل إعدادات اللعبة الأخرى"explanation="description for advanced options"max="38*5"max_local="38*5"/>
<stringenglish="unfocus pause"translation="إزالة تركيز الراحة"explanation="menu option. Turns the pause screen on/off, which shows up when the window is unfocused/inactive (only one window is normally in focus/active at a time). Possible alternative: auto pause screen"/>
<stringenglish="Unfocus Pause"translation="إزالة تركيز الراحة"explanation="title. Turns the pause screen on/off, which shows up when the window is unfocused/inactive (only one window is normally in focus/active at a time). Possible alternative: auto pause screen"max="20"max_local="20"/>
<stringenglish="Toggle if the game will pause when the window is unfocused."translation="يحدد هل ستتوقف اللعبة لو زال التركيز عن النافذة."explanation=""max="38*3"max_local="38*3"/>
<stringenglish="Unfocus pause is OFF"translation="تركيز الراحة معطل"explanation="Making another window active will not show the pause screen."max="38*2"max_local="38*2"/>
<stringenglish="Unfocus pause is ON"translation="تركيز الراحة مفعل"explanation="Making another window active will show the pause screen."max="38*2"max_local="38*2"/>
<stringenglish="unfocus audio pause"translation="الكتم عند زوال التركيز"explanation="menu option. Allows the user to choose whether the music should pause, or continue to play, when the window is unfocused/inactive (only one window is normally in focus/active at a time). Possible alternative: auto audio pause"/>
<stringenglish="Unfocus Audio"translation="الكتم عند زوال التركيز"explanation="title. Allows the user to choose whether the music should pause, or continue to play, when the window is unfocused/inactive (only one window is normally in focus/active at a time). Possible alternative: auto audio pause"max="20"max_local="20"/>
<stringenglish="Toggle if the audio will pause when the window is unfocused."translation="يحدد هل سيكتم صوت اللعبة لو زال التركيز عن النافذة."explanation=""max="38*3"max_local="38*3"/>
<stringenglish="Unfocus audio pause is OFF"translation="الصمت عند زوال التركيز معطل"explanation="Making another window active will leave the music keep playing."max="38*2"max_local="38*2"/>
<stringenglish="Unfocus audio pause is ON"translation="الصمت عند زوال التركيز مفعل"explanation="Making another window active will pause the music."max="38*2"max_local="38*2"/>
<stringenglish="toggle in-game timer"translation="خيار إظهار المؤقت أثناء اللعبة"explanation="menu option"/>
<stringenglish="In-Game Timer"translation="المؤقت أثناء اللعبة"explanation="title"max="20"max_local="20"/>
<stringenglish="Toggle the in-game timer outside of time trials."translation="إظهار المؤقت في اللعبة حتى لغير التحدي ضد الساعة."explanation=""max="38*3"max_local="38*3"/>
<stringenglish="In-Game Timer is ON"translation="المؤقت أثناء اللعبة مفعل"explanation=""max="38*2"max_local="38*2"/>
<stringenglish="In-Game Timer is OFF"translation="المؤقت أثناء اللعبة معطل"explanation=""max="38*2"max_local="38*2"/>
<stringenglish="Show the original English word enemies regardless of your language setting."translation="تظهر صور الأعداء بنسختها الإنكليزية الأصلية، بصرف النظر عن اللغة المختارة."explanation=""max="38*3"max_local="38*3"/>
<stringenglish="Sprites are currently translated"translation="صور الأعداء مترجمة الآن"explanation=""max="38*2"max_local="38*2"/>
<stringenglish="Sprites are currently ALWAYS ENGLISH"translation="صور الأعداء دوما بالإنكليزية الآن"explanation=""max="38*2"max_local="38*2"/>
<stringenglish="Interact Button"translation="زر التفاعل"explanation="title, lets the user change the key for interacting with objects or crewmates"max="20"max_local="20"/>
<stringenglish="Toggle whether you interact with prompts using ENTER or E."translation="هل التفاعل مع النصوص بزر ENTER أو E."explanation="prompts: see the `Press {button} to talk to .../activate terminal/teleport` below"max="38*3"max_local="38*3"/>
<stringenglish="E"translation="E"explanation="keyboard key E. Speedrunner options menu"/>
<stringenglish="ACTION"translation="زر الفعل"explanation="the ACTION key is either the SPACE key, Z or V (this is explained on the title screen). It's used in strings like `Press ACTION to advance text`"/>
<stringenglish="Interact button: {button}"translation="زر التفاعل: {button}"explanation="keyboard key (E or ENTER) is filled in for {button}. Speedrunner options menu"max="38*2"max_local="38*2"/>
<stringenglish="Fake Load Screen"translation="شاشة التحميل المزيفة"explanation="title, allows the loading screen which counts to 100% to be turned off"max="20"max_local="20"/>
<stringenglish="Disable the fake loading screen which appears on game launch."translation="تعطيل شاشة التحميل المزيفة التي تظهر قبل اللعبة."explanation=""max="38*3"max_local="38*3"/>
<stringenglish="Fake loading screen is OFF"translation="شاشة التحميل المزيفة معطلة"explanation="allows the loading screen which counts to 100% to be turned off"max="38*2"max_local="38*2"/>
<stringenglish="Fake loading screen is ON"translation="شاشة التحميل المزيفة تعمل"explanation="allows the loading screen which counts to 100% to be turned off"max="38*2"max_local="38*2"/>
<stringenglish="room name background"translation="خلفية اسم الغرفة"explanation="menu option, background behind room names"/>
<stringenglish="Room Name BG"translation="خلفية اسم الغرفة"explanation="title, background behind room names"max="20"max_local="20"/>
<stringenglish="Lets you see through what is behind the name at the bottom of the screen."translation="وضوح رؤية ما وراء اسم الغرفة أسفل الشاشة."explanation=""max="38*3"max_local="38*3"/>
<stringenglish="Room name background is TRANSLUCENT"translation="خلفية اسم الغرفة شفافة"explanation=""max="38*2"max_local="38*2"/>
<stringenglish="Room name background is OPAQUE"translation="خلفية اسم الغرفة ليست شفافة"explanation=""max="38*2"max_local="38*2"/>
<stringenglish="checkpoint saving"translation="تخزين نقطة الحفظ"explanation="menu option"/>
<stringenglish="Checkpoint Saving"translation="تخزين نقطة الحفظ"explanation="title, makes checkpoints save the game"max="20"max_local="20"/>
<stringenglish="Toggle if checkpoints should save the game."translation="تفعيل تخزين التقدم عند كل نقطة حفظ."explanation=""max="38*3"max_local="38*3"/>
<stringenglish="Checkpoint saving is OFF"translation="نقاط الحفظ لا تخزن التقدم"explanation="makes checkpoints save the game"max="38*2"max_local="38*2"/>
<stringenglish="Checkpoint saving is ON"translation="نقاط الحفظ تخزن التقدم"explanation="makes checkpoints save the game"max="38*2"max_local="38*2"/>
<stringenglish="Access some advanced settings that might be of interest to speedrunners."translation="دخولإعداداتمتقدمةقدتحظىباهتمام
من يختمون الألعاب بسرعة." explanation="description for speedrunner options" max="38*5" max_local="38*5"/>
<stringenglish="glitchrunner mode"translation="إعدادات صائد العيوب"explanation="menu option, a glitchrunner is a speedrunner who takes advantage of glitches to run through the game faster"/>
<stringenglish="Glitchrunner Mode"translation="إعدادات صائد العيوب"explanation="title, a glitchrunner is a speedrunner who takes advantage of glitches to run through the game faster"max="20"max_local="20"/>
<stringenglish="Re-enable glitches that existed in previous versions of the game."translation="صائد العيوب يستغلها ليختم اللعبة بسرعة. هذا الوضع يعيد تفعيل عيوب برمجية من إصدارات سابقة سبق إصلاحها."explanation="glitchrunner mode"max="38*3"max_local="38*3"/>
<stringenglish="Glitchrunner mode is OFF"translation="وضع صائد العيوب السريع معطل."explanation=""max="38*2"max_local="38*2"/>
<stringenglish="Glitchrunner mode is {version}"translation="وضع صائد العيوب السريع مفعل."explanation="a version number is filled in for {version}, such as 2.0 or 2.2"max="38*2"max_local="38*2"/>
<stringenglish="Select a new glitchrunner version below."translation="يرجى اختيار إصدار جديد لصائد العيوب السريع."explanation=""max="38*3"max_local="38*3"/>
<stringenglish="none"translation="بدون"explanation="menu option, do not emulate any older version"/>
<stringenglish="2.0"translation="2.0"explanation="VVVVVV version number for glitchrunner mode"/>
<stringenglish="2.2"translation="2.2"explanation="VVVVVV version number for glitchrunner mode"/>
<stringenglish="input delay"translation="تأجيل الضغطة"explanation="menu option, enable 1 frame of delay after pressing input"/>
<stringenglish="Input Delay"translation="تأجيل الضغطة"explanation="title, enable 1 frame of delay after pressing input"max="20"max_local="20"/>
<stringenglish="Re-enable the 1-frame input delay from previous versions of the game."translation="إرجاع تأجيل الضغطة بإطار واحد كما كان الحال في تحديثات اللعبة السابقة"explanation="input delay"max="38*3"max_local="38*3"/>
<stringenglish="Input delay is ON"translation="تأجيل الضغطة يعمل"explanation=""max="38*2"max_local="38*2"/>
<stringenglish="Input delay is OFF"translation="تأجيل الضغطة معطل"explanation=""max="38*2"max_local="38*2"/>
<stringenglish="Disable screen effects, enable slowdown modes or invincibility."translation="تعطيل بعض مؤثرات الشاشة، تفعيل أطوار لإبطاء اللعبة أو للحصانة من الأذى."explanation=""max="38*5"max_local="38*5"/>
<stringenglish="Disable animated backgrounds in menus and during gameplay."translation="تعطيل الخلفيات المتحركة في القوائم وأثناء اللعب."explanation=""max="38*3"max_local="38*3"/>
<stringenglish="Backgrounds are ON."translation="الخلفيات تعمل."explanation=""max="38*2"max_local="38*2"/>
<stringenglish="Backgrounds are OFF."translation="الخلفيات معطلة."explanation=""max="38*2"max_local="38*2"/>
<stringenglish="Explore the game freely without dying. (Can cause glitches.)"translation="للتجول بحرية في اللعبة بدون أن تموت. قد تظهر عيوب برمجية."explanation="invincibility mode"max="38*3"max_local="38*3"/>
<stringenglish="Invincibility is ON."translation="الحصانة تعمل."explanation=""max="38*2"max_local="38*2"/>
<stringenglish="Invincibility is OFF."translation="الحصانة معطلة."explanation=""max="38*2"max_local="38*2"/>
<stringenglish="Are you sure you want to enable invincibility?"translation="ستفعلالحصانةمنالأضرار.
<stringenglish="Who do you want to play the level with?"translation="مع من أردت لعب هذا المستوى؟"explanation="choose your NPC companion"max="38*8"max_local="38*8"/>
<stringenglish="time trials"translation="تحدي ضد الساعة"explanation="menu option"/>
<stringenglish="Time Trials"translation="تحدي ضد الساعة"explanation="title"max="20"max_local="20"/>
<stringenglish="Replay any level in the game in a competitive time trial mode."translation="يمكنك إعادة أي مستوى في اللعبة في طور تنافسي ضد الساعة."explanation=""max="38*4"max_local="38*4"/>
<stringenglish="Time Trials are not available with slowdown or invincibility."translation="تحدي ضد الساعة لا يتوفر مع الحصانة أو التبطئة."explanation=""max="38*3"max_local="38*3"/>
<stringenglish="unlock time trials"translation="فتح تحدي ضد الساعة"explanation="menu option"/>
<stringenglish="Unlock Time Trials"translation="فتح تحدي ضد الساعة"explanation="title"max="20"max_local="20"/>
<stringenglish="You can unlock each time trial separately."translation="يمكنك فتح كل تحدي ضد الساعة على حدة."explanation=""max="38*5"max_local="38*5"/>
<stringenglish="Replay the intermission levels."translation="إعادة لعب مراحل وصلات الفواصل"explanation=""max="38*3"max_local="38*3"/>
<stringenglish="unlock intermissions"translation="فتح وصلات الفواصل"explanation="menu option"/>
<stringenglish="TO UNLOCK: Complete the intermission levels in-game."translation="الشرط: ختم مستويات وصلات الفواصل أثناء اللعب."explanation=""max="38*4"max_local="38*4"/>
<stringenglish="no death mode"translation="تحدي بدون الموت"explanation="menu option"/>
<stringenglish="No Death Mode"translation="تحدي بدون الموت"explanation="title"max="20"max_local="20"/>
<stringenglish="Play the entire game without dying once."translation="تختيم اللعبة بأكملها دون الموت ولا مرة."explanation=""max="38*4"max_local="38*4"/>
<stringenglish="No Death Mode is not available with slowdown or invincibility."translation="تحدي بدون الموت لا يتوفر مع الحصانة أو التبطئة."explanation=""max="38*3"max_local="38*3"/>
<stringenglish="unlock no death mode"translation="فتح تحدي بدون الموت"explanation="menu option"/>
<stringenglish="TO UNLOCK: Achieve an S-rank or above in at least 4 time trials."translation="الشرط: تحقيق الرتبة S أو ما فوق في 4 تحديات ضد الساعة على الأقل."explanation="ranks are B A S V, see below"max="38*3"max_local="38*3"/>
<stringenglish="flip mode"translation="الوضع المعكوس"explanation="menu option, mirrors the entire game vertically"/>
<stringenglish="Flip Mode"translation="الوضع المعكوس"explanation="title, mirrors the entire game vertically"max="20"max_local="20"/>
<stringenglish="Flip the entire game vertically."translation="يعكس اللعبة بأكملها عموديا."explanation=""max="38*2"max_local="38*2"/>
<stringenglish="Flip the entire game vertically. Compatible with other game modes."translation="يعكس اللعبة بأكملها عموديا. يتوافق مع أطوار اللعبة الأخرى."explanation=""max="38*4"max_local="38*4"/>
<stringenglish="unlock flip mode"translation="فتح الوضع المعكوس"explanation="menu option"/>
<stringenglish="You managed to reach:"translation="نجحت في وصول الغرفة:"explanation="you managed to reach the following room"max="40"max_local="40"/>
<stringenglish="Keep trying! You'll get there!"translation="فلنواصل المحاولة! سندرك هدفنا!"explanation="player died before managing to save anybody"max="38*2"max_local="38*2"/>
<stringenglish="Nice one!"translation="جميل!"explanation="player died after saving one crewmate"max="38*2"max_local="38*2"/>
<stringenglish="Wow! Congratulations!"translation="واو! مبارك، تهانينا!"explanation="player died after saving two crewmates"max="38*2"max_local="38*2"/>
<stringenglish="Incredible!"translation="شيء مذهل!"explanation="player died after saving three crewmates"max="38*2"max_local="38*2"/>
<stringenglish="Unbelievable! Well done!"translation="لا أصدق! أحسنت صنيعا!"explanation="player died after saving four crewmates"max="38*2"max_local="38*2"/>
<stringenglish="Er, how did you do that?"translation="هع، أنى لك هذا؟"explanation="player died even though they were finished, lol"max="38*2"max_local="38*2"/>
<stringenglish="You rescued all the crewmates!"translation="أنقذت كل أفراد الطاقم!"explanation=""max="40"max_local="40"/>
<stringenglish="A new trophy has been awarded and placed in the secret lab to acknowledge your achievement!"translation="أهديت جائزة جديدة ووضعت في المخبر السري إشادة بإنجازك!"explanation=""max="38*4"max_local="38*4"/>
<stringenglish="[Trinkets found]"translation="[ التذكارات التي وجدتها ]"explanation="amount of shiny trinkets found"max="40"max_local="40"/>
<stringenglish="[Number of Deaths]"translation="[ الميتات التي تكبدتها ]"explanation=""max="40"max_local="40"/>
<stringenglish="[Time Taken]"translation="[ الوقت الذي استغرقته ]"explanation="stopwatch time"max="40"max_local="40"/>
<stringenglish="Trinkets Found:"translation="التذكارات التي وجدتها:"explanation="game complete screen"max="22"max_local="22"/>
<stringenglish="{n_trinkets} of {max_trinkets}"translation="{n_trinkets} من أصل {max_trinkets}"explanation="ex: 2 of 5"/>
<stringenglish="{n_trinkets|wordy} out of {max_trinkets|wordy}"translation="{n_trinkets|wordy} من أصل {max_trinkets|wordy}"explanation="ex: One out of Twenty, see numbers.xml. You can add |upper for an uppercase letter."max="34"max_local="34"/>
<stringenglish="{savebox_n_trinkets|wordy}"translation="{savebox_n_trinkets|wordy}"explanation="trinket count in telesave/quicksave information box. You can add |upper for an uppercase letter."/>
<stringenglish="{gamecomplete_n_trinkets|wordy}"translation="{gamecomplete_n_trinkets|wordy}"explanation="trinket count on Game Complete screen (after Trinkets Found:) You can add |upper for an uppercase letter."/>
<stringenglish="+1 Rank!"translation="+1 للرتبة!"explanation="time trial rank was upgraded (B → A → S → V). B is minimum, which is purposefully high (see it as 7/10) - S is a popular rank above A in a lot of games, so VVVVVV added a rank above that."max="12"max_local="12"/>
<stringenglish="Not yet attempted"translation="لم تجرب من قبل"explanation=""max="38*2"max_local="38*2"/>
<stringenglish="TO UNLOCK:"translation="شرط الفتح:"explanation="followed by `Rescue XX`/`Complete the game`, and then `Find XX trinkets`"max="40"max_local="40"/>
<stringenglish="Complete the game"translation="ختم اللعبة"case="0"explanation=""max="40"max_local="40"/>
<stringenglish="Find three trinkets"translation="إيجاد ثلاث تذكارات"explanation=""max="40"max_local="40"/>
<stringenglish="Find six trinkets"translation="إيجاد ست تذكارات"explanation=""max="40"max_local="40"/>
<stringenglish="Find nine trinkets"translation="إيجاد تسع تذكارات"explanation=""max="40"max_local="40"/>
<stringenglish="Find twelve trinkets"translation="إيجاد إثنتا عشر تذكار"explanation=""max="40"max_local="40"/>
<stringenglish="Find fifteen trinkets"translation="إيجاد خمسة عشر تذكار"explanation=""max="40"max_local="40"/>
<stringenglish="Find eighteen trinkets"translation="إيجاد ثمانية عشر تذكار"explanation=""max="40"max_local="40"/>
<stringenglish="RECORDS"translation="السجلات"explanation="followed by a list of personal bests for TIME, SHINY and LIVES. So `records` as in `world records`, except for yourself"max="15"max_local="15"/>
<stringenglish="Your save files have been updated."translation="حدثت بيانات تخزيناتك."explanation="player completed game"max="38*6"max_local="38*6"/>
<stringenglish="If you want to keep exploring the game, select CONTINUE from the play menu."translation="لو شئت مواصلة استكشاف اللعبة، فعليك بخيار المواصلة من قائمة اللعب."explanation=""max="38*9"max_local="38*9"/>
<stringenglish="You have unlocked a new Time Trial."translation="فتحت تحديا من جملة التحديات ضد الساعة."explanation=""max="38*7"max_local="38*7"/>
<stringenglish="You have unlocked some new Time Trials."translation="فتحت تحديا جديدا من جملة التحديات ضد الساعة."explanation=""max="38*7"max_local="38*7"/>
<stringenglish="You have unlocked No Death Mode."translation="فتحت تحدي بدون الموت."explanation=""max="38*7"max_local="38*7"/>
<stringenglish="You have unlocked Flip Mode."translation="فتحت الوضع المعكوس."explanation=""max="38*7"max_local="38*7"/>
<stringenglish="You have unlocked the intermission levels."translation="فتحت مستويات وصلات الفواصل."explanation=""max="38*7"max_local="38*7"/>
<stringenglish="play a level"translation="لعب مرحلة"explanation="menu option"/>
<stringenglish="open level folder"translation="فتح مجلد المراحل"explanation="menu option. Button that opens the folder with level files in a file explorer"/>
<stringenglish="back to levels"translation="العودة للمستويات"explanation="menu option"/>
<stringenglish="The level editor is not currently supported on Steam Deck, as it requires a keyboard and mouse to use."translation="محرر المستويات يحتاج لوح مفاتيح وفأرة. لهذا السبب، ليس مدعوما على منصة Steam Deck."explanation=""max="38*5"max_local="38*5"/>
<stringenglish="The level editor is not currently supported on this device, as it requires a keyboard and mouse to use."translation="محرر المستويات يحتاج لوح مفاتيح وفأرة. لهذا السبب، ليس مدعوما على جهازك."explanation=""max="38*5"max_local="38*5"/>
<stringenglish="To install new player levels, copy the .vvvvvv files to the levels folder."translation="لتنصيب مراحل اللاعبين الجديدة، تنسخ ملفات .vvvvvv إلى مجلد levels."explanation=""max="38*5"max_local="38*5"/>
<stringenglish="Are you sure you want to show the levels path? This may reveal sensitive information if you are streaming."translation="قبل إظهار مسار المستويات، يرجى التأكد لتجنب افتضاح أي معلومات حساسة أثناء البثوث. هل نظهره؟"explanation=""max="38*4"max_local="38*4"/>
<stringenglish="ACTION = Space, Z, or V"translation="زر الفعل = المسافة أو Z أو V"explanation="title screen"max="38*3"max_local="38*3"/>
<stringenglish="[Press {button} to return to editor]"translation="[نضغط {button} للعودة للمحرر]"explanation="`to editor` is sorta redundant"max="40"max_local="40"/>
<stringenglish="- Press {button} to advance text -"translation="- نضغط {button} للتقدم في النص -"explanation="to dismiss a textbox. Expect `ACTION`"max="40"max_local="40"/>
<stringenglish="Press {button} to continue"translation="نضغط {button} للمواصلة"explanation="Expect `ACTION`"max="34"max_local="34"/>
<stringenglish="[Press {button} to unfreeze gameplay]"translation="[نضغط {button} لوقف تجميد اللعب العادي]"explanation="in level debugger: {button} makes everything start moving as normal. Limit is treacherous, expect TAB for {button}. Frozen is the initial state, so this is the first string of the two that users will see!"max="39"max_local="39"/>
<stringenglish="[Press {button} to freeze gameplay]"translation="[نضغط {button} لتجميد اللعب العادي]"explanation="in level debugger: {button} makes everything stop moving. Limit is treacherous, expect TAB for {button}."max="39"max_local="39"/>
<stringenglish="NO SIGNAL"translation="لا إشارة"explanation="map screen. So like a TV/computer monitor"max="29"max_local="29"/>
<stringenglish="Press {button} to warp to the ship."translation="للتنقيل إلى السفينة، نضغط {button}."explanation="spaceship. Warp = teleport. Expect `ACTION`"max="38*7"max_local="38*7"/>
<stringenglish="Missing..."translation="مفقود..."case="1"explanation="this male crew member is missing"max="15"max_local="15"/>
<stringenglish="Missing..."translation="مفقودة..."case="2"explanation="this female crew member is missing"max="15"max_local="15"/>
<stringenglish="Missing..."translation="أين أنت..."case="3"explanation="Viridian is missing (final level). You could even fill in something like `Uh-oh...` here if you really have to specify gender otherwise - everyone else is rescued, but the player is missing"max="15"max_local="15"/>
<stringenglish="Rescued!"translation="أنقذته!"case="1"explanation="this male crew member is not missing anymore"max="15"max_local="15"/>
<stringenglish="Rescued!"translation="أنقذتها!"case="2"explanation="this female crew member is not missing anymore"max="15"max_local="15"/>
<stringenglish="(that's you!)"translation="(ها أنت!)"explanation="this crew member is you (Viridian)"max="15"max_local="15"/>
<stringenglish="Cannot Save in Level Replay"translation="لا يحق الحفظ عند مشاهدة المستوى"explanation="in-game menu"max="38*7"max_local="38*7"/>
<stringenglish="Cannot Save in No Death Mode"translation="لا يحق الحفظ في تحدي بدون الموت"explanation="in-game menu"max="38*7"max_local="38*7"/>
<stringenglish="How'd you get here?"translation="كيف وصلت هنا؟"explanation="in-game menu"max="38*7"max_local="38*7"/>
<stringenglish="Cannot Save in Secret Lab"translation="لا يحق الحفظ في المخبر السري"explanation="in-game menu"max="38*7"max_local="38*7"/>
<stringenglish="ERROR: Could not save game!"translation="خطأ: فشل حفظ تقدم اللعبة!"explanation="in-game menu"max="34*2"max_local="34*2"/>
<stringenglish="ERROR: Could not save settings file!"translation="خطأ: فشل حفظ ملف الإعدادات!"explanation=""max="38*2"max_local="38*2"/>
<stringenglish="Game saved ok!"translation="حفظت اللعبة على خير!"explanation="in-game menu"max="38*2"max_local="38*2"/>
<stringenglish="[Press {button} to save your game]"translation="[نضغط {button} لحفظ لعبتك]"explanation="in-game menu. Expect `ACTION`"max="40"max_local="40"/>
<stringenglish="(Note: The game is autosaved at every teleporter.)"translation="(اللعبة تحفظ تلقائيا عند كل آلة تنقيل.)"explanation="in-game menu"max="38*3"max_local="38*3"/>
<stringenglish="Return to main menu?"translation="هل بودك العودة للقائمة الرئيسية؟"explanation="in-game menu"max="38*4"max_local="38*4"/>
<stringenglish="Do you want to quit? You will lose any unsaved progress."translation="هلقررتالمغادرة؟قديضيع
أي تقدم لم يحفظ بعد." explanation="in-game menu" max="38*4" max_local="38*4"/>
<stringenglish="Do you want to return to the secret laboratory?"translation="هل بودك العودة للمخبر السري؟"explanation="in-game menu"max="38*4"max_local="38*4"/>
<stringenglish="no, keep playing"translation="لا، سأواصل اللعب"explanation="in-game menu option"max="28"max_local="28"/>
<stringenglish="[ NO, KEEP PLAYING ]"translation="[ لا، سأواصل اللعب ]"explanation="in-game menu option"max="32"max_local="32"/>
<stringenglish="yes, quit to menu"translation="نعم، سأغادر"explanation="in-game menu option"max="24"max_local="24"/>
<stringenglish="[ YES, QUIT TO MENU ]"translation="[ نعم، سأغادر ]"explanation="in-game menu option"max="28"max_local="28"/>
<stringenglish="yes, return"translation="نعم، سأعود"explanation="in-game menu option"max="24"max_local="24"/>
<stringenglish="[ YES, RETURN ]"translation="[ نعم، سأعود ]"explanation="in-game menu option"max="28"max_local="28"/>
<stringenglish="no, return"translation="لا، سأعود"explanation="quit program menu option"/>
<stringenglish="yes, quit"translation="نعم، سأخرج"explanation="quit program menu option"/>
<stringenglish="return to game"translation="العودة للعبة"explanation="pause menu option"max="27"max_local="27"/>
<stringenglish="quit to menu"translation="الخروج للقائمة"explanation="pause menu option"max="19"max_local="19"/>
<stringenglish="Press Left/Right to choose a Teleporter"translation="نضغط يسار/يمين لاختيار آلة تنقيل"explanation="tight fit, so maybe `Left/Right: choose teleporter`, or use ←/→"max="40"max_local="40"/>
<stringenglish="Press {button} to Teleport"translation="نضغط {button} للتنقل"explanation="keyboard key (E or ENTER) or controller button glyph is filled in for {button}. max is when displayed"max="40"max_local="40"/>
<stringenglish="- Press {button} to Teleport -"translation="- نضغط {button} للتنقل -"explanation="keyboard key (E or ENTER) or controller button glyph is filled in for {button}"max="40"max_local="40"/>
<stringenglish="Press {button} to explode"translation="نضغط {button} للتفجر"explanation="keyboard key (E or ENTER) or controller button glyph is filled in for {button}. max is when displayed. This was for testing, but people sometimes find it in custom levels"max="37"max_local="37"/>
<stringenglish="Press {button} to talk to Violet"translation="نضغط {button} للحديث مع فايولت"explanation="keyboard key (E or ENTER) or controller button glyph is filled in for {button}. max is when displayed"max="37"max_local="37"/>
<stringenglish="Press {button} to talk to Vitellary"translation="نضغط {button} للحديث مع فيتيلاري"explanation="keyboard key (E or ENTER) or controller button glyph is filled in for {button}. max is when displayed"max="37"max_local="37"/>
<stringenglish="Press {button} to talk to Vermilion"translation="نضغط {button} للحديث مع فارميليون"explanation="keyboard key (E or ENTER) or controller button glyph is filled in for {button}. max is when displayed"max="37"max_local="37"/>
<stringenglish="Press {button} to talk to Verdigris"translation="نضغط {button} للحديث مع فارديغري"explanation="keyboard key (E or ENTER) or controller button glyph is filled in for {button}. max is when displayed"max="37"max_local="37"/>
<stringenglish="Press {button} to talk to Victoria"translation="نضغط {button} للحديث مع فيكتوريا"explanation="keyboard key (E or ENTER) or controller button glyph is filled in for {button}. max is when displayed"max="37"max_local="37"/>
<stringenglish="Press {button} to activate terminal"translation="نضغط {button} لتفعيل الحاسب"explanation="keyboard key (E or ENTER) or controller button glyph is filled in for {button}. max is when displayed"max="37"max_local="37"/>
<stringenglish="Press {button} to activate terminals"translation="نضغط {button} لتفعيل الحواسيب"explanation="keyboard key (E or ENTER) or controller button glyph is filled in for {button}. max is when displayed. there are 3 next to each other in the ship"max="37"max_local="37"/>
<stringenglish="Press {button} to interact"translation="نضغط {button} للتفاعل"explanation="keyboard key (E or ENTER) or controller button glyph is filled in for {button}. max is when displayed"max="37"max_local="37"/>
<stringenglish="- Press {button} to skip -"translation="- نضغط {button} للتجاوز -"explanation="keyboard key (ENTER) or controller button glyph is filled in for {button}. max is when displayed. This prompt is for skipping cutscenes"max="40"max_local="40"/>
<stringenglish="Passion for Exploring"translation="Passion for Exploring"explanation="jukebox prompt. song name should probably not be translated"max="37*2"max_local="37*2"/>
<stringenglish="Pushing Onwards"translation="Pushing Onwards"explanation="jukebox prompt. song name should probably not be translated"max="37*2"max_local="37*2"/>
<stringenglish="Positive Force"translation="Positive Force"explanation="jukebox prompt. song name should probably not be translated"max="37*2"max_local="37*2"/>
<stringenglish="Presenting VVVVVV"translation="Presenting VVVVVV"explanation="jukebox prompt. song name should probably not be translated"max="37*2"max_local="37*2"/>
<stringenglish="Potential for Anything"translation="Potential for Anything"explanation="jukebox prompt. song name should probably not be translated"max="37*2"max_local="37*2"/>
<stringenglish="Predestined Fate"translation="Predestined Fate"explanation="jukebox prompt. song name should probably not be translated"max="37*2"max_local="37*2"/>
<stringenglish="Pipe Dream"translation="Pipe Dream"explanation="jukebox prompt. song name should probably not be translated"max="37*2"max_local="37*2"/>
<stringenglish="Popular Potpourri"translation="Popular Potpourri"explanation="jukebox prompt. song name should probably not be translated"max="37*2"max_local="37*2"/>
<stringenglish="Pressure Cooker"translation="Pressure Cooker"explanation="jukebox prompt. song name should probably not be translated"max="37*2"max_local="37*2"/>
<stringenglish="ecroF evitisoP"translation="ecroF evitisoP"explanation="jukebox prompt. song name should probably not be translated"max="37*2"max_local="37*2"/>
<stringenglish="Map Settings"translation="إعدادات الغرفة"explanation="title, editor, Level Settings, map is kinda inconsistent with everything else"max="20"max_local="20"/>
<stringenglish="edit scripts"translation="تحرير السكربتات"explanation="level editor menu option"/>
<stringenglish="change music"translation="تغيير الموسيقى"explanation="level editor menu option"/>
<stringenglish="editor ghosts"translation="أشباح المحرر"explanation="level editor menu option. Toggles option to show a repetition of the player's path after playtesting"/>
<stringenglish="Editor ghost trail is OFF"translation="مسار شبح المحرر معطل"explanation="level editor. Repetition of the player's path after playtesting is disabled"max="40"max_local="40"/>
<stringenglish="Editor ghost trail is ON"translation="مسار شبح المحرر مفعل"explanation="level editor. Repetition of the player's path after playtesting is enabled"max="40"max_local="40"/>
<stringenglish="load level"translation="فتح المستوى"explanation="level editor menu option"/>
<stringenglish="save level"translation="حفظ المستوى"explanation="level editor menu option"/>
<stringenglish="quit to main menu"translation="الخروج للقائمة الرئيسية"explanation="level editor menu option"max="22"max_local="22"/>
<stringenglish="change name"translation="تبديل الاسم"explanation="level editor menu option"/>
<stringenglish="change author"translation="تبديل المؤلف"explanation="level editor menu option"/>
<stringenglish="change description"translation="تبديل الوصف"explanation="level editor menu option"/>
<stringenglish="change website"translation="تبديل موقع النت"explanation="level editor menu option"/>
<stringenglish="change font"translation="تبديل الخطوط"explanation="level editor menu option"/>
<stringenglish="Level Font"translation="خط نص المستوى"explanation="title, editor, font that a custom level will show up in. You can select a language name here (like Chinese or Japanese which have different fonts, or `other`)"max="20"max_local="20"/>
<stringenglish="Select the language in which the text in this level is written."translation="اختيار اللغة التي كتب بها نص هذا المستوى."explanation=""max="38*3"max_local="38*3"/>
<stringenglish="Font: "translation="الخط: "explanation="level options, followed by name of font (mind the space)"max="15"max_local="15"/>
<stringenglish="Map Music"translation="موسيقى الغرفة"explanation="title, editor, music that starts playing when a level is started. Can be changed with scripting later, so this is really just initial music"max="20"max_local="20"/>
<stringenglish="Current map music:"translation="موسيقى الغرفة الحالية:"explanation="editor, followed by the number and name of a song, or `No background music`. Can be changed with scripting later, so this is really just initial music"max="38*2"max_local="38*2"/>
<stringenglish="No background music"translation="لا موسيقى"explanation="editor, level starts with no song playing"max="38*2"max_local="38*2"/>
<stringenglish="1: Pushing Onwards"translation="1: Pushing Onwards"explanation="editor, song name should probably not be translated"max="38*2"max_local="38*2"/>
<stringenglish="2: Positive Force"translation="2: Positive Force"explanation="editor, song name should probably not be translated"max="38*2"max_local="38*2"/>
<stringenglish="3: Potential for Anything"translation="3: Potential for Anything"explanation="editor, song name should probably not be translated"max="38*2"max_local="38*2"/>
<stringenglish="4: Passion for Exploring"translation="4: Passion for Exploring"explanation="editor, song name should probably not be translated"max="38*2"max_local="38*2"/>
<stringenglish="N/A: Pause"translation="N/A: Pause"explanation="editor, song name should probably not be translated"max="38*2"max_local="38*2"/>
<stringenglish="5: Presenting VVVVVV"translation="5: Presenting VVVVVV"explanation="editor, song name should probably not be translated"max="38*2"max_local="38*2"/>
<stringenglish="N/A: Plenary"translation="N/A: Plenary"explanation="editor, song name should probably not be translated"max="38*2"max_local="38*2"/>
<stringenglish="6: Predestined Fate"translation="6: Predestined Fate"explanation="editor, song name should probably not be translated"max="38*2"max_local="38*2"/>
<stringenglish="N/A: ecroF evitisoP"translation="N/A: ecroF evitisoP"explanation="editor, song name should probably not be translated"max="38*2"max_local="38*2"/>
<stringenglish="7: Popular Potpourri"translation="7: Popular Potpourri"explanation="editor, song name should probably not be translated"max="38*2"max_local="38*2"/>
<stringenglish="8: Pipe Dream"translation="8: Pipe Dream"explanation="editor, song name should probably not be translated"max="38*2"max_local="38*2"/>
<stringenglish="9: Pressure Cooker"translation="9: Pressure Cooker"explanation="editor, song name should probably not be translated"max="38*2"max_local="38*2"/>
<stringenglish="10: Paced Energy"translation="10: Paced Energy"explanation="editor, song name should probably not be translated"max="38*2"max_local="38*2"/>
<stringenglish="11: Piercing the Sky"translation="11: Piercing the Sky"explanation="editor, song name should probably not be translated"max="38*2"max_local="38*2"/>
<stringenglish="N/A: Predestined Fate Remix"translation="N/A: Predestined Fate Remix"explanation="editor, song name should probably not be translated"max="38*2"max_local="38*2"/>
<stringenglish="?: something else"translation="?: شيء آخر"explanation="editor, song was not recognized"max="38*2"max_local="38*2"/>
<stringenglish="next song"translation="اللحن التالي"explanation="level editor menu option"/>
<stringenglish="previous song"translation="اللحن السابق"explanation="level editor menu option"/>
<stringenglish="back"translation="عودة"explanation="level editor menu option"/>
<stringenglish="Save before quitting?"translation="هل تود الحفظ قبل الخروج؟"explanation="level editor"max="38*4"max_local="38*4"/>
<stringenglish="yes, save and quit"translation="أجل، حفظ ثم خروج"explanation="level editor menu option"/>
<stringenglish="no, quit without saving"translation="لا، خروج بدون حفظ"explanation="level editor menu option"/>
<stringenglish="return to editor"translation="عودة للمحرر"explanation="level editor menu option"/>
<stringenglish="Untitled Level"translation="مستوى بلا عنوان"explanation=""max="20"max_local="20"/>
<stringenglish="SCRIPT BOX: Click on the first corner"translation="هامش السكربت: انقر الزاوية الأولى للصندوق"explanation="editor, a script box is an invisible box that runs a script when touched"max="39*3"max_local="39*3"/>
<stringenglish="SCRIPT BOX: Click on the last corner"translation="هامش السكربت: انقر الزاوية المقابلة للصندوق"explanation="editor, a script box is an invisible box that runs a script when touched"max="39*3"max_local="39*3"/>
<stringenglish="ENEMY BOUNDS: Click on the first corner"translation="هامش الأعداء: انقر الزاوية الأولى للصندوق"explanation="editor, invisible box which enemies always stay inside of"max="39*3"max_local="39*3"/>
<stringenglish="ENEMY BOUNDS: Click on the last corner"translation="هامش الأعداء: انقر الزاوية المقابلة للصندوق"explanation="editor, invisible box which enemies always stay inside of"max="39*3"max_local="39*3"/>
<stringenglish="PLATFORM BOUNDS: Click on the first corner"translation="هامش المنصات: انقر الزاوية الأولى للصندوق"explanation="editor, invisible box which moving platforms always stay inside of"max="39*3"max_local="39*3"/>
<stringenglish="PLATFORM BOUNDS: Click on the last corner"translation="هامش المنصات: انقر الزاوية المقابلة للصندوق"explanation="editor, invisible box which moving platforms always stay inside of"max="39*3"max_local="39*3"/>
<stringenglish="Click on the first corner"translation="انقر الزاوية الأولى"explanation=""max="39*3"max_local="39*3"/>
<stringenglish="Click on the last corner"translation="انقر الزاوية الأخيرة"explanation=""max="39*3"max_local="39*3"/>
<stringenglish="**** VVVVVV SCRIPT EDITOR ****"translation="**** محرر سكربتات VVVVVV ****"explanation="supposed to look like a Commodore 64 screen"max="36"max_local="36"/>
<stringenglish="PRESS ESC TO RETURN TO MENU"translation="اضغط ESC للعودة للقائمة"explanation="Commodore 64-style script editor, TO MENU is redundant"max="36"max_local="36"/>
<stringenglish="NO SCRIPT IDS FOUND"translation="لم نجد معرف ID للسكربتات"explanation="Commodore 64-style script editor, basically NO SCRIPTS FOUND"max="36"max_local="36"/>
<stringenglish="CREATE A SCRIPT WITH EITHER THE TERMINAL OR SCRIPT BOX TOOLS"translation="أنشئ سكربتا إما بالواجهة النصية أو بأدوات السكربتات"explanation="Commodore 64-style script editor"max="36*5"max_local="36*5"/>
<stringenglish="CURRENT SCRIPT: {name}"translation="السكربت الحالي: {name}"explanation="Commodore 64-style script editor. Char limit is soft, but the longer this is, the more often users" script names run offscreen. Consider SCRIPT: instead"max="20"max_local="20"/>
<stringenglish="Left click to place warp destination"translation="نقرة يسرى بالفأرة لوضع وجهة التنقيل"explanation="warp token: small teleporter with entrance and destination"max="39"max_local="39"/>
<stringenglish="Right click to cancel"translation="نقرة يمنى بالفأرة للتراجع"explanation=""max="39"max_local="39"/>
<stringenglish="{button1} and {button2} keys change tool"translation="الأزرار {button1} و {button2} لتغيير الأداة"explanation="These keys can be used to switch between tools"max="36"max_local="36"/>
<stringenglish="5: Checkpoints"translation="5: نقاط حفظ"explanation="editor tool. You restart at the last checkpoint after death"max="32"max_local="32"/>
<stringenglish="6: Disappearing Platforms"translation="6: منصات تتلاشى"explanation="editor tool. Platform that disappears when you step on it (disappearing platform, crumbling platform)"max="32"max_local="32"/>
<stringenglish="0: Gravity Lines"translation="0: خطوط جاذبية"explanation="editor tool. Gravity line, which flips your gravity when touching it"max="32"max_local="32"/>
<stringenglish="T: Terminals"translation="T: حواسيب"explanation="editor tool. Computer which can be activated by the player to run a script"max="32"max_local="32"/>
<stringenglish="Y: Script Boxes"translation="Y: هامش سكربت"explanation="editor tool. Invisible box that runs a script when touched"max="32"max_local="32"/>
<stringenglish="U: Warp Tokens"translation="U: نائب تنقيل"explanation="editor tool. Small teleporter with entrance and destination"max="32"max_local="32"/>
<stringenglish="I: Warp Lines"translation="I: خط تنقيل"explanation="editor tool. Makes a room edge be connected to its opposite edge"max="32"max_local="32"/>
<stringenglish="O: Crewmates"translation="O: عضو طاقم"explanation="editor tool. Crewmate that can be rescued"max="32"max_local="32"/>
<stringenglish="P: Start Point"translation="P: نقطة بدء"explanation="editor tool"max="32"max_local="32"/>
<stringenglish="START"translation="بدء"explanation="start point in level editor"max="10"max_local="10"/>
<stringenglish="SPACE ^ SHIFT ^"translation="SPACE ^ SHIFT ^"explanation="editor, indicates both SPACE key and SHIFT key open up menus. ^ is rendered as up arrow"max="32"max_local="32"/>
<stringenglish="F1: Change Tileset"translation="F1: تبديل مجموعة الخلايا"explanation="editor shortcut, switch to different tileset"max="25"max_local="25"/>
<stringenglish="F2: Change Colour"translation="F2: تبديل الألوان"explanation="editor shortcut, switch to different tileset color/variant"max="25"max_local="25"/>
<stringenglish="F10: Direct Mode"translation="F10: وضع الرسم المباشر"explanation="editor shortcut, direct mode is manual tile placing mode, where walls are not automatically made nice"max="25"max_local="25"/>
<stringenglish="W: Change Warp Dir"translation="W: تبديل اتجاه التنقيل"explanation="editor shortcut, dir=direction, change which edges of the room wrap around. Can be horizontal, vertical or all sides"max="25"max_local="25"/>
<stringenglish="E: Change Roomname"translation="E: تبديل اسم الغرفة"explanation="editor shortcut, the name of the room appears at the bottom"max="25"max_local="25"/>
<stringenglish="S: Save Map"translation="S: حفظ الغرفة"explanation="level editor, save level, `Map` is basically redundant"max="12"max_local="12"/>
<stringenglish="L: Load Map"translation="L: فتح الغرفة"explanation="level editor, load level, `Map` is basically redundant"max="12"max_local="12"/>
<stringenglish="Enter map filename to save as:"translation="اكتب مسار ملف الغرفة لحفظه:"explanation="level editor text input, save level file as"max="39*3"max_local="39*3"/>
<stringenglish="Enter map filename to load:"translation="اكتب مسار ملف الغرفة لفتحه:"explanation="level editor text input, load level file"max="39*3"max_local="39*3"/>
<stringenglish="Enter new room name:"translation="اكتب اسم الغرفة الجديدة:"explanation="level editor text input, the name of the room appears at the bottom"max="39*3"max_local="39*3"/>
<stringenglish="Enter room coordinates x,y:"translation="اكتب احداثيات الغرفة س,ص:"explanation="level editor text input, go to another room by its coordinates, for example, 9,15"max="39*3"max_local="39*3"/>
<stringenglish="Enter script name:"translation="اكتب اسم السكربت:"explanation="level editor text input, enter name of newly created script, or edit the script name of a terminal/script box"max="39*3"max_local="39*3"/>
<stringenglish="Enter roomtext:"translation="اكتب النص:"explanation="level editor text input, enter text to place in the room"max="39*3"max_local="39*3"/>
<stringenglish="Space Station"translation="المحطة الفضائية "explanation="editor tileset name, Now using Space Station Tileset"/>
<stringenglish="Outside"translation="الفضاء الخارجي"explanation="editor tileset name, Now using Outside Tileset"/>
<stringenglish="Lab"translation="المخبر"explanation="editor tileset name, Now using Lab Tileset"/>
<stringenglish="Warp Zone"translation="منطقة التنقيل"explanation="editor tileset name, Now using Warp Zone Tileset. The Warp Zone got its name because its rooms wrap around, but think Star Trek"/>
<stringenglish="Ship"translation="السفينة"explanation="editor tileset name, Now using Ship Tileset. Spaceship"/>
<stringenglish="Now using {area} Tileset"translation="سنغيرلمجموعةالخلايا
من {area}" explanation="level editor, user changed the tileset of the room to {area} (like Ship, Lab, etc)" max="38*3" max_local="38*3"/>
<stringenglish="Tileset Colour Changed"translation="تغير لون مجموعة الخلايا"explanation="level editor, user changed the tileset colour/variant of the room"max="38*3"max_local="38*3"/>
<stringenglish="Enemy Type Changed"translation="تغير نوع الأعداء"explanation="level editor, user changed enemy appearance for the room"max="38*3"max_local="38*3"/>
<stringenglish="Platform speed is now {speed}"translation="تغيرت سرعة المنصات إلى {speed}"explanation="level editor, user changed speed of platforms for the room"max="38*3"max_local="38*3"/>
<stringenglish="Enemy speed is now {speed}"translation="تغيرت سرعة الأعداء إلى {speed}"explanation="level editor, user changed speed of enemies for the room"max="38*3"max_local="38*3"/>
<stringenglish="Reloaded resources"translation="أعيد فتح ملفات الموارد"explanation="level editor, reloaded graphics assets/resources, music and sound effects"max="38*3"max_local="38*3"/>
<stringenglish="ERROR: Invalid format"translation="خطأ: صيغة المكتوب غير مناسبة"explanation="user was supposed to enter something like `12,12`, but entered `as@df`"max="38*3"max_local="38*3"/>
<stringenglish="ERROR: Could not load level"translation="خطأ: فشل فتح الغرفة"explanation="that level could not be loaded, maybe it does not exist"max="38*3"max_local="38*3"/>
<stringenglish="ERROR: Could not save level!"translation="خطأ: فشل حفظ الغرفة!!"explanation="maybe the filename is too long? exclamation mark because you will lose your data if you quit"max="38*3"max_local="38*3"/>
<stringenglish="Mapsize is now [{width},{height}]"translation="أبعاد الغرفة الآن [{width},{height}]"explanation="editor, the map is now {width} by {height}"max="38*3"max_local="38*3"/>
<stringenglish="Direct Mode Disabled"translation="وضع الرسم المباشر معطل"explanation="editor, manual tile placing mode where walls are not automatically made nice, now changed to automatic mode"max="38*3"max_local="38*3"/>
<stringenglish="Direct Mode Enabled"translation="وضع الرسم المباشر مفعل"explanation="editor, manual tile placing mode where walls are not automatically made nice, now changed to manual mode"max="38*3"max_local="38*3"/>
<stringenglish="ERROR: Warp lines must be on edges"translation="خطأ: خطوط التنقيل ترسم على حواف الغرفة"explanation="level editor, warp lines make a room edge be connected to its opposite edge. So they must be placed on one of the edges, not in the middle"max="38*3"max_local="38*3"/>
<stringenglish="Room warps in all directions"translation="الغرفة تلتف على نفسها من كل الاتجاهات"explanation="level editor, all edges of the room are connected with each other. If the player leaves the room they end up on the other side of the same room."max="38*3"max_local="38*3"/>
<stringenglish="Room warps horizontally"translation="الغرفة تلتف على نفسها أفقيا"explanation="level editor, the left and right edges of the room are connected with each other. The player can still go to other rooms on the top and bottom"max="38*3"max_local="38*3"/>
<stringenglish="Room warps vertically"translation="الغرفة تلتف على نفسها عموديا"explanation="level editor, the top and bottom edges of the room are connected with each other. The player can still go to other rooms on the left and right"max="38*3"max_local="38*3"/>
<stringenglish="Room warping disabled"translation="الغرفة لا تلتف على نفسها"explanation="level editor, no edges are connected and the player can go to other rooms"max="38*3"max_local="38*3"/>
<stringenglish="ERROR: No checkpoint to spawn at"translation="خطأ: لا نقطة حفظ يبعث اللاعب منها"explanation="we cannot playtest because there is no checkpoint in this room that the player could start (be spawned) at"max="38*3"max_local="38*3"/>
<stringenglish="ERROR: Max number of trinkets is 100"translation="خطأ: أقصى عدد للمقتنيات 100"explanation="editor, user tried to place another trinket"max="38*3"max_local="38*3"/>
<stringenglish="ERROR: Max number of crewmates is 100"translation="خطأ: أقصى عدد لأفراد الطاقم 100"explanation="editor, user tried to place another crewmate"max="38*3"max_local="38*3"/>
<stringenglish="Level quits to menu"translation="يغادر المستوى إلى القائمة"explanation="editor message, user would have been forcefully returned to title screen but wasn't"max="38*3"max_local="38*3"/>
<stringenglish="Level completed"translation="يختم المستوى"explanation="editor message, user would have been returned to levels list but wasn't"max="38*3"max_local="38*3"/>
<stringenglish="Rolled credits"translation="تظهر شاشة فريق العمل"explanation="editor message, credits would have been shown but weren't"max="38*3"max_local="38*3"/>
<stringenglish="Time trial completed"translation="تظهر شاشة ختم التحدي ضد الساعة"explanation="editor message, time trial complete screen would have been shown but wasn't"max="38*3"max_local="38*3"/>
<stringenglish="{hrs}:{min|digits=2}:{sec|digits=2}"translation="{hrs}:{min|digits=2}:{sec|digits=2}"explanation="time format H:MM:SS"/>
<stringenglish="{hrs}:{min|digits=2}:{sec|digits=2}.{cen|digits=2}"translation="{hrs}:{min|digits=2}:{sec|digits=2}.{cen|digits=2}"explanation="time format H:MM:SS.CC"/>
<stringenglish="{min}:{sec|digits=2}"translation="{min}:{sec|digits=2}"explanation="time format M:SS"/>
<stringenglish="{min}:{sec|digits=2}.{cen|digits=2}"translation="{min}:{sec|digits=2}.{cen|digits=2}"explanation="time format M:SS.CC"/>
<stringenglish="{sec}.{cen|digits=2}"translation="{sec}.{cen|digits=2}"explanation="time format S.CC"/>
<stringenglish=".99"translation=".99"explanation="appended to time format for 99/100 seconds (example: 1:15.99). Time trial results"/>
<stringenglish="{area}, {time}"translation="{area} / {time}"explanation="saved game summary, e.g. `Space Station, 12:30:59`"/>
<stringenglish="Level Complete!"translation="ختمت المستوى!"explanation="Might be tight, the exclamation mark may be removed"max="18"max_local="18"/>
<stringenglish="Game Complete!"translation="ختمت اللعبة!"explanation="Might be tight, the exclamation mark may be removed"max="18"max_local="18"/>
<stringenglish="You have rescued a crew member!"translation="أنقذت أحد أفراد طاقمك!"explanation="If you need to manually wordwrap: please ensure this has exactly two lines. Ignore the (font-adapted) maximum if it says 1 line."max="30*2"max_local="30*2"/>
<stringenglish="All Crew Members Rescued!"translation="أنقذت جميع طاقمك!"explanation=""max="32"max_local="32"/>
<stringenglish="All crewmates rescued!"translation="أنقذت جميع طاقمك!"explanation=""max="32"max_local="32"/>
<stringenglish="Press arrow keys or WASD to move"translation="تضغط الأسهم أو WASD للحركة"explanation=""max="32*2"max_local="32*2"/>
<stringenglish="Press left/right to move"translation="نضغط يسار/يمين للحركة"explanation=""max="32*2"max_local="32*2"/>
<stringenglish="Press {button} to flip"translation="نضغط {button} للعكس"explanation="expect `ACTION`"max="32*3"max_local="32*3"/>
<stringenglish="Press {button} to view map and quicksave"translation="نضغط {button} لرؤية الخريطة والتخزين السريع"explanation=""max="32*3"max_local="32*3"/>
<stringenglish="If you prefer, you can press UP or DOWN instead of ACTION to flip."translation="لو شئت، يمكنك للعكس ضغط فوق/تحت عوضا عن زر الفعل."explanation=""max="34*3"max_local="34*3"/>
<stringenglish="Help! Can anyone hear this message?"translation="النجدة!! هل من سميع لهذه الرسالة؟"explanation="Violet speaking via Comms Relay"max="25*4"max_local="25*4"/>
<stringenglish="Verdigris? Are you out there? Are you ok?"translation="فارديغري؟ أهذا أنت في الخارج؟ هل صحتك بخير؟"explanation="Violet speaking via Comms Relay"max="25*4"max_local="25*4"/>
<stringenglish="Please help us! We've crashed and need assistance!"translation="رجاء أنجدونا! سقطت سفينتنا ونحتاج مدد الدعم!"explanation="Violet speaking via Comms Relay"max="25*4"max_local="25*4"/>
<stringenglish="Hello? Anyone out there?"translation="ألو، ألو؟ هل من أحد يسمعني؟"explanation="Violet speaking via Comms Relay"max="25*4"max_local="25*4"/>
<stringenglish="This is Doctor Violet from the D.S.S. Souleye! Please respond!"translation="هنا الدكتورة فايولت، من سفينة D.S.S. سولاي! رجاء أجيبوني!"explanation="Violet speaking via Comms Relay"max="25*4"max_local="25*4"/>
<stringenglish="Please... Anyone..."translation="ليت أحدا يرد... أترجاكم..."explanation="Violet speaking via Comms Relay"max="25*4"max_local="25*4"/>
<stringenglish="Please be alright, everyone..."translation="أن تعودوا جميعا... سالمين..."explanation="Violet speaking via Comms Relay"max="25*4"max_local="25*4"/>
<stringenglish="Congratulations!
You have found a shiny trinket!" translation="تهانينا!
وجدت تذكارا لماعا!" explanation="" max="34*4" max_local="34*4"/>
<stringenglish="Congratulations!
You have found a lost crewmate!" translation="تهانينا!
وجدت أحد أفراد الطاقم التائهين!" explanation="" max="34*4" max_local="34*4"/>
<stringenglish="Congratulations!
You have found the secret lab!" translation="تهانينا!
وجدت المخبر السري!" explanation="" max="34*4" max_local="34*4"/>
<stringenglish="The secret lab is separate from the rest of the game. You can now come back here at any time by selecting the new SECRET LAB option in the play menu."translation="المخبر السري جزء معزول عن بقية اللعبة. يمكنك العودة متى شئت بخيار مخصص له سيضاف في قائمة اللعب."explanation=""max="36*10"max_local="36*10"/>
<stringenglish="Viridian"translation="فيريديان"explanation="crewmate name (player)"max="15"max_local="15"/>
<stringenglish="Vitellary"translation="فيتيلاري"case="1"explanation="crewmate name as menu option: Who do you want to play the level with?"/>
<stringenglish="Vermilion"translation="فارميليون"case="1"explanation="crewmate name as menu option: Who do you want to play the level with?"/>
<stringenglish="Verdigris"translation="فارديغري"case="1"explanation="crewmate name as menu option: Who do you want to play the level with?"/>
<stringenglish="Victoria"translation="فيكتوريا"case="1"explanation="crewmate name as menu option: Who do you want to play the level with?"/>
<stringenglish="Starring"translation="بطولة"explanation="credits roll. Starring the following 6 crew members"max="20"max_local="20"/>
<stringenglish="Captain Viridian"translation="القبطان فيريديان"explanation="credits roll. Starring the following 6 crew members"max="27"max_local="27"/>
<stringenglish="Doctor Violet"translation="الدكتورة فايوليت"explanation="credits roll. Starring the following 6 crew members"max="27"max_local="27"/>
<stringenglish="Professor Vitellary"translation="الأستاذ فيتيلاري"explanation="credits roll. Starring the following 6 crew members"max="27"max_local="27"/>
<stringenglish="Officer Vermilion"translation="النقيب فارميليون"explanation="credits roll. Starring the following 6 crew members"max="27"max_local="27"/>
<stringenglish="Chief Verdigris"translation="القائد فارديغري"explanation="credits roll. Starring the following 6 crew members"max="27"max_local="27"/>
<stringenglish="Doctor Victoria"translation="الدكتورة فيكتوريا"explanation="credits roll. Starring the following 6 crew members"max="27"max_local="27"/>
<stringenglish="When you're standing on the floor, Vitellary will try to walk to you."translation="أثناء وقوفك على الأرضية، سيحاول فيتيلاري المشي نحوك."explanation="Intermission 1"max="34*4"max_local="34*4"/>
<stringenglish="When you're standing on the floor, Vermilion will try to walk to you."translation="أثناء وقوفك على الأرضية، سيحاول فارميليون المشي نحوك."explanation="Intermission 1"max="34*4"max_local="34*4"/>
<stringenglish="When you're standing on the floor, Verdigris will try to walk to you."translation="أثناء وقوفك على الأرضية، سيحاول فارديغري المشي نحوك."explanation="Intermission 1"max="34*4"max_local="34*4"/>
<stringenglish="When you're standing on the floor, Victoria will try to walk to you."translation="أثناء وقوفك على الأرضية، ستحاول فيكتوريا المشي نحوك."explanation="Intermission 1"max="34*4"max_local="34*4"/>
<stringenglish="When you're standing on the floor, your companion will try to walk to you."translation="أثناء وقوفك على الأرضية، سيحاول مرافقك أيا كان المشي نحوك."explanation="Intermission 1, unknown companion, normally impossible but reproducible in custom levels"max="34*4"max_local="34*4"/>
<stringenglish="When you're standing on the ceiling, Vitellary will try to walk to you."translation="أثناء وقوفك على السقف، سيحاول فيتيلاري المشي نحوك."explanation="Intermission 1 in flip mode"max="34*4"max_local="34*4"/>
<stringenglish="When you're standing on the ceiling, Vermilion will try to walk to you."translation="أثناء وقوفك على السقف، سيحاول فارميليون المشي نحوك."explanation="Intermission 1 in flip mode"max="34*4"max_local="34*4"/>
<stringenglish="When you're standing on the ceiling, Verdigris will try to walk to you."translation="أثناء وقوفك على السقف، سيحاول فارديغري المشي نحوك."explanation="Intermission 1 in flip mode"max="34*4"max_local="34*4"/>
<stringenglish="When you're standing on the ceiling, Victoria will try to walk to you."translation="أثناء وقوفك على السقف، ستحاول فيكتوريا المشي نحوك."explanation="Intermission 1 in flip mode"max="34*4"max_local="34*4"/>
<stringenglish="When you're standing on the ceiling, your companion will try to walk to you."translation="أثناء وقوفك على السقف، سيحاول مرافقك أيا كان المشي نحوك."explanation="Intermission 1 in flip mode, unknown companion, normally impossible but reproducible in custom levels"max="34*4"max_local="34*4"/>
<stringenglish="When you're NOT standing on the floor, Vitellary will stop and wait for you."translation="إن كفيت عن ملامسة الأرضية، سيتوقف فيتيلاري وينتظرك."explanation="Intermission 1"max="34*4"max_local="34*4"/>
<stringenglish="When you're NOT standing on the floor, Vermilion will stop and wait for you."translation="إن كفيت عن ملامسة الأرضية، سيتوقف فارميليون وينتظرك."explanation="Intermission 1"max="34*4"max_local="34*4"/>
<stringenglish="When you're NOT standing on the floor, Verdigris will stop and wait for you."translation="إن كفيت عن ملامسة الأرضية، سيتوقف فارديغري وينتظرك."explanation="Intermission 1"max="34*4"max_local="34*4"/>
<stringenglish="When you're NOT standing on the floor, Victoria will stop and wait for you."translation="إن كفيت عن ملامسة الأرضية، ستتوقف فيكتوريا وتنتظرك."explanation="Intermission 1"max="34*4"max_local="34*4"/>
<stringenglish="When you're NOT standing on the floor, your companion will stop and wait for you."translation="إن كفيت عن ملامسة الأرضية، سيتوقف مرافقك أيا كان وينتظرك."explanation="Intermission 1, unknown companion, normally impossible but reproducible in custom levels"max="34*4"max_local="34*4"/>
<stringenglish="When you're NOT standing on the ceiling, Vitellary will stop and wait for you."translation="إن كفيت عن ملامسة السقف، سيتوقف فيتيلاري وينتظرك."explanation="Intermission 1 in flip mode"max="34*4"max_local="34*4"/>
<stringenglish="When you're NOT standing on the ceiling, Vermilion will stop and wait for you."translation="إن كفيت عن ملامسة السقف، سيتوقف فارميليون وينتظرك."explanation="Intermission 1 in flip mode"max="34*4"max_local="34*4"/>
<stringenglish="When you're NOT standing on the ceiling, Verdigris will stop and wait for you."translation="إن كفيت عن ملامسة السقف، سيتوقف فارديغري وينتظرك."explanation="Intermission 1 in flip mode"max="34*4"max_local="34*4"/>
<stringenglish="When you're NOT standing on the ceiling, Victoria will stop and wait for you."translation="إن كفيت عن ملامسة السقف، ستتوقف فيكتوريا وتنتظرك."explanation="Intermission 1 in flip mode"max="34*4"max_local="34*4"/>
<stringenglish="When you're NOT standing on the ceiling, your companion will stop and wait for you."translation="إن كفيت عن ملامسة السقف، سيتوقف مرافقك أيا كان وينتظرك."explanation="Intermission 1 in flip mode, unknown companion, normally impossible but reproducible in custom levels"max="34*4"max_local="34*4"/>
<stringenglish="You can't continue to the next room until he is safely across."translation="لا يمكنك المواصلة للغرفة القادمة قبل أن يعبر سالما."explanation="Intermission 1"max="34*4"max_local="34*4"/>
<stringenglish="You can't continue to the next room until she is safely across."translation="لا يمكنك المواصلة للغرفة القادمة قبل أن تعبر سالمة."explanation="Intermission 1"max="34*4"max_local="34*4"/>
<stringenglish="You can't continue to the next room until they are safely across."translation="لا يمكنك المواصلة للغرفة القادمة قبل أن تعبرا سالمين."explanation="Intermission 1, unknown companion, normally impossible but reproducible in custom levels"max="34*4"max_local="34*4"/>
<stringenglish="Survive for"translation="سأحاول الصمود"explanation="Gravitron. Line 1/2: Survive for 60 seconds!"max="20"max_local="20"/>
<stringenglish="60 seconds!"translation="لمدة 60 ثانية!"explanation="Gravitron. Line 2/2: Survive for 60 seconds!"max="20"max_local="20"/>
<stringenglish="Thanks for"translation="شكرا على"explanation="credits. Line 1/2: Thanks for playing!"max="20"max_local="20"/>
<stringenglish="playing!"translation="اللعب!"explanation="credits. Line 2/2: Thanks for playing!"max="20"max_local="20"/>
<stringenglish="SPACE STATION 1 MASTERED"translation="احترفت المحطة الفضائية 1"explanation="achievement/trophy title"max="38*2"max_local="38*2"/>
<stringenglish="LABORATORY MASTERED"translation="احترفت المخبر"explanation="achievement/trophy title - V rank in time trial"max="38*2"max_local="38*2"/>
<stringenglish="THE TOWER MASTERED"translation="احترفت البرج"explanation="achievement/trophy title - V rank in time trial"max="38*2"max_local="38*2"/>
<stringenglish="SPACE STATION 2 MASTERED"translation="احترفت المحطة الفضائية 2"explanation="achievement/trophy title - V rank in time trial"max="38*2"max_local="38*2"/>
<stringenglish="WARP ZONE MASTERED"translation="احترفت منطقة التنقيل"explanation="achievement/trophy title - V rank in time trial"max="38*2"max_local="38*2"/>
<stringenglish="FINAL LEVEL MASTERED"translation="احترفت المستوى الأخير"explanation="achievement/trophy title - V rank in time trial"max="38*2"max_local="38*2"/>
<stringenglish="Obtain a V Rank in this Time Trial"translation="حصلت على رتبة V في هذا التحدي ضد الساعة."explanation="achievement/trophy description"max="38*2"max_local="38*2"/>
<stringenglish="Complete the game"translation="ختمت اللعبة"case="1"explanation="achievement/trophy description"max="38*2"max_local="38*2"/>
<stringenglish="FLIP MODE COMPLETE"translation="ختم الوضع المعكوس"explanation="achievement/trophy title"max="38*2"max_local="38*2"/>
<stringenglish="Complete the game in flip mode"translation="ختمت اللعبة في الوضع المعكوس"explanation="achievement/trophy description"max="38*2"max_local="38*2"/>
<stringenglish="Win with less than 50 deaths"translation="ختمت اللعبة بأقل من 50 ميتة"explanation="achievement/trophy description"max="38*2"max_local="38*2"/>
<stringenglish="Win with less than 100 deaths"translation="ختمت اللعبة بأقل من 100 ميتة"explanation="achievement/trophy description"max="38*2"max_local="38*2"/>
<stringenglish="Win with less than 250 deaths"translation="ختمت اللعبة بأقل من 250 ميتة"explanation="achievement/trophy description"max="38*2"max_local="38*2"/>
<stringenglish="Win with less than 500 deaths"translation="ختمت اللعبة بأقل من 500 ميتة"explanation="achievement/trophy description"max="38*2"max_local="38*2"/>
<stringenglish="Last 5 seconds on the Super Gravitron"translation="صمدت 5 ثوان في أتون الجاذبية الخارق"explanation="achievement/trophy description"max="38*2"max_local="38*2"/>
<stringenglish="Last 10 seconds on the Super Gravitron"translation="صمدت 10 ثوان في أتون الجاذبية الخارق"explanation="achievement/trophy description"max="38*2"max_local="38*2"/>
<stringenglish="Last 15 seconds on the Super Gravitron"translation="صمدت 15 ثانية في أتون الجاذبية الخارق"explanation="achievement/trophy description"max="38*2"max_local="38*2"/>
<stringenglish="Last 20 seconds on the Super Gravitron"translation="صمدت 20 ثانية في أتون الجاذبية الخارق"explanation="achievement/trophy description"max="38*2"max_local="38*2"/>
<stringenglish="Last 30 seconds on the Super Gravitron"translation="صمدت 30 ثانية في أتون الجاذبية الخارق"explanation="achievement/trophy description"max="38*2"max_local="38*2"/>
<stringenglish="Last 1 minute on the Super Gravitron"translation="صمدت دقيقة في أتون الجاذبية الخارق"explanation="achievement/trophy description"max="38*2"max_local="38*2"/>
<stringenglish="MASTER OF THE UNIVERSE"translation="المحترف الكوني"explanation="achievement/trophy title - no death mode complete"max="38*2"max_local="38*2"/>
<stringenglish="Complete the game in no death mode"translation="ختمت اللعبة في تحدي بدون الموت."explanation="achievement/trophy description"max="38*2"max_local="38*2"/>
<stringenglish="Something went wrong, but we forgot the error message."translation="حصلت مشكلة، لكن نسينا كتابة رسالة خطأ مناسبة."explanation="the message that is printed in case the game detects an error but there's no error message set"max="38*6"max_local="38*6"/>
<stringenglish="Could not mount {path}: real directory doesn't exist"translation="فشل ربط المسار {path}: لا وجود للمجلد الحقيقي"explanation="mount: link/attach a directory (folder) in the filesystem into the game's filesystem so we can access it"max="38*6"max_local="38*6"/>
<stringenglish="Level {path} not found"translation="لا وجود للمرحلة {path}"explanation=""max="38*6"max_local="38*6"/>
<stringenglish="Error parsing {path}: {error}"translation="فشل فتح {path}: {error}"explanation="we tried to parse the level file, but failed"max="38*6"max_local="38*6"/>
<stringenglish="{filename} dimensions not exact multiples of {width} by {height}!"translation="الأبعاد في {filename} ليست مضاعفات صحيحة لقيم العرض {width} والارتفاع {height}!"explanation="filename is something like tiles.png, tiles2.png, etc. and width/height are something like 8, 32, etc.; this is used if the dimensions of a graphics file aren't an exact multiple of the given size (e.g. 8x8, 32x32, etc.)"max="38*6"max_local="38*6"/>
<stringenglish="ERROR: Could not write to language folder! Make sure there is no "lang" folder next to the regular saves."translation="خطأ: فشلت الكتابة نحو مجلد اللغة! احرص أن لا مجلد "lang" بالقرب من التخزينات العادية"explanation=""max="38*5"max_local="38*5"/>
<!-- Please read README.txt for information about the language files -->
<strings_pluralmax_local_for="8x10">
<stringenglish_plural="You rescued {n_crew|wordy} crewmates"english_singular="You rescued {n_crew|wordy} crewmate"explanation="These two strings are displayed underneath each other (1/2)"max="40"var="n_crew"expect="6"max_local="40">
<translationform="0"translation="لم ننقذ أي فرد من الطاقم"/>
<translationform="1"translation="أنقذت فردا من الطاقم"/>
<translationform="2"translation="أنقذت فردين من الطاقم"/>
<translationform="3"translation="أنقذت {n_crew|wordy} أفراد من الطاقم"/>
<translationform="11"translation="أنقذت {n_crew|wordy} فرد من الطاقم"/>
</string>
<stringenglish_plural="and found {n_trinkets|wordy} trinkets."english_singular="and found {n_trinkets|wordy} trinket."explanation="These two strings are displayed underneath each other (2/2)"max="38*2"var="n_trinkets"expect="20"max_local="38*2">
<translationform="0"translation="ولم نجد أي تذكار."/>
<stringenglish_plural="And you found {n_trinkets|wordy} trinkets."english_singular="And you found {n_trinkets|wordy} trinket."explanation="You rescued all the crewmates! And you found XX trinket(s)."max="38*3"var="n_trinkets"expect="20"max_local="38*3">
<translationform="0"translation="ولم نجد أي تذكار."/>
<stringenglish_plural="{n_crew|wordy} crewmates remain"english_singular="{n_crew|wordy} crewmate remains"explanation="Reminder: you can add |upper for an uppercase letter."max="38*2"var="n_crew"expect="100"max_local="38*2">
<translationform="0"translation="لم يبق أي فرد من الطاقم"/>
<translationform="1"translation="بقي فرد من الطاقم"/>
<translationform="2"translation="بقي فردان من الطاقم"/>
<translationform="3"translation="بقي {n_crew|wordy} أفراد من الطاقم"/>
<translationform="11"translation="بقي {n_crew|wordy} فردا من الطاقم"/>
</string>
<stringenglish_plural="{n_crew|wordy} remain"english_singular="{n_crew|wordy} remains"explanation="You have rescued a crew member! XX remain"max="32*2"var="n_crew"expect="100"max_local="32*2">
<translationform="0"translation="لم يبق أي فرد من الطاقم"/>
<translationform="1"translation="بقي فرد من الطاقم"/>
<translationform="2"translation="بقي فردان من الطاقم"/>
<translationform="3"translation="بقي {n_crew|wordy} أفراد من الطاقم"/>
<translationform="11"translation="بقي {n_crew|wordy} فردا من الطاقم"/>
<translationform="0"translation="أصعب غرفة (بدون ميتات)"/>
<translationform="1"translation="أصعب غرفة (ميتة واحدة)"/>
<translationform="2"translation="أصعب غرفة (ميتتان)"/>
<translationform="3"translation="أصعب غرفة ({n_deaths|wordy2} ميتات)"/>
<translationform="11"translation="أصعب غرفة ({n_deaths|wordy2} ميتة)"/>
</string>
<stringenglish_plural="{n} normal room names untranslated"english_singular="{n} normal room name untranslated"explanation="per-area counts for room name translator mode"max="38*4"var="n"expect="48"max_local="38*4">
<translationform="0"translation="لم يبق أي اسم غرفة لم يترجم"/>
<translationform="1"translation="اسم غرفة عادي لم يترجم"/>
<translationform="2"translation="اسما غرفتين عادينين لم يترجما"/>
<translationform="3"translation="لم يترجم {n|wordy} أسماء لغرف عادية"/>
<translationform="11"translation="لم يترجم {n|wordy} اسم لغرف عادية"/>
<dialoguespeaker="purple"english="Something has gone horribly wrong with the ship's teleporter!"translation="Ha passat alguna cosa terrible amb el teletransportador de la nau!"/>
<dialoguespeaker="purple"english="I think everyone has been teleported away randomly! They could be anywhere!"translation="Em sembla que ha teletransportat tothom a llocs aleatoris! Podrien ser en qualsevol lloc!"/>
<dialoguespeaker="purple"english="I'm on the ship - it's damaged badly, but it's still intact!"translation="Jo sóc a la nau. Està molt malmesa,|però encara està sencera!"/>
<dialoguespeaker="purple"english="Where are you, Captain?"translation="Tu on ets, cap?"/>
<dialoguespeaker="cyan"english="I'm on some sort of space station... It seems pretty modern..."translation="En alguna mena d’estació espacial...|Sembla força moderna..."/>
<dialoguespeaker="purple"english="There seems to be some sort of interference in this dimension..."translation="Sembla que en aquesta dimensió hi ha alguna mena d’interferència..."/>
<dialoguespeaker="purple"english="I'm broadcasting the coordinates of the ship to you now."translation="Ara t’enviaré les coordenades de la nau."/>
<dialoguespeaker="purple"english="I can't teleport you back, but..."translation="No et puc teletransportar fins aquí, però..."/>
<dialoguespeaker="purple"english="If YOU can find a teleporter anywhere nearby, you should be able to teleport back to me!"translation="Si aconsegueixes trobar tu un teletransportador en algun lloc a prop d’on ets, hauries de poder-te teletransportar fins a la nau!"/>
<dialoguespeaker="cyan"english="Ok! I'll try to find one!"translation="Entesos! Miraré de trobar-ne un!"/>
<dialoguespeaker="purple"english="I'll keep trying to find the rest of the crew..."translation="Jo continuaré mirant de contactar amb la resta de la tripulació..."/>
</cutscene>
<cutsceneid="trenchwarfare"explanation="player finds Trench Warfare trinket, if no trinkets found yet">
<dialoguespeaker="cyan"english="Ohh! I wonder what that is?"translation="Oooh! Què deu ser, aquella cosa?"/>
<dialoguespeaker="cyan"english="I probably don't really need it, but it might be nice to take it back to the ship to study..."translation="Suposo que no la necessito, però potser estaria bé dur-la a la nau per a estudiar-la..."/>
</cutscene>
<cutsceneid="newtrenchwarfare"explanation="player finds Trench Warfare trinket, if other trinket already found">
<dialoguespeaker="cyan"english="Oh! It's another one of those shiny things!"translation="Oh! Una altra cosa brillant d’aquelles!"/>
<dialoguespeaker="cyan"english="I probably don't really need it, but it might be nice to take it back to the ship to study..."translation="Suposo que no la necessito, però potser estaria bé dur-la a la nau per a estudiar-la..."/>
<dialoguespeaker="player"english="So, Doctor - have you any idea what caused the crash?"translation="I doncs, doctora... Tens cap idea de què ha fet que ens estavelléssim?"/>
<dialoguespeaker="purple"english="There's some sort of bizarre signal here that's interfering with our equipment..."translation="Hi ha alguna mena de senyal estrany|que provoca interferències als nostres sistemes..."/>
<dialoguespeaker="purple"english="It caused the ship to lose its quantum position, collapsing us into this dimension!"translation="Ha fet que la nau perdés la posició quàntica i ens ha endinsat en aquesta dimensió!"/>
<dialoguespeaker="purple"english="But I think we should be able to fix the ship and get out of here..."translation="Però em sembla que hauríem de poder reparar la nau i sortir d’aquí..."/>
<dialoguespeaker="purple"english="... as long as we can find the rest of the crew."translation="Sempre que trobem la resta de la tripulació, és clar..."/>
<dialoguespeaker="purple"english="We really don't know anything about this place..."translation="No en sabem absolutament res, d’aquest lloc..."/>
<dialoguespeaker="purple"english="Our friends could be anywhere - they could be lost, or in danger!"translation="Els nostres amics podrien ser en qualsevol racó. I podrien estar perduts o en perill!"/>
<dialoguespeaker="player"english="Can they teleport back here?"translation="Es poden teletransportar fins aquí?"/>
<dialoguespeaker="purple"english="Not unless they find some way to communicate with us!"translation="Si no troben cap manera de comunicar-se amb nosaltres, no!"/>
<dialoguespeaker="purple"english="We can't pick up their signal and they can't teleport here unless they know where the ship is..."translation="No rebem cap senyal seu i no es poden teletransportar fins aquí si no saben on és la nau..."/>
<dialoguespeaker="player"english="So what do we do?"translation="I què fem, doncs?"/>
<dialoguespeaker="purple"english="We need to find them! Head out into the dimension and look for anywhere they might have ended up..."translation="Hem de trobar-los! Surt a la dimensió i mira de descobrir on han anat a parar..."/>
<dialoguespeaker="player"english="Ok! Where do we start?"translation="D’acord! Per on comencem?"/>
<dialoguespeaker="purple"english="Well, I've been trying to find them with the ship's scanners!"translation="Bé, jo he mirat de localitzar-los amb els escàners de la nau!"/>
<dialoguespeaker="purple"english="It's not working, but I did find something..."translation="No ha funcionat, però he descobert una cosa..."/>
<dialoguespeaker="purple"english="These points show up on our scans as having high energy patterns!"translation="Aquests punts que apareixen a l’escaneig tenen patrons d’alta energia!"/>
<dialoguespeaker="purple"english="There's a good chance they're teleporters - which means they're probably built near something important..."translation="És força probable que siguin teletransportadors, i això vol dir que estan construïts al costat de coses importants..."/>
<dialoguespeaker="purple"english="They could be a very good place to start looking."translation="Poden ser un molt bon lloc on començar a investigar."/>
<dialoguespeaker="player"english="Ok! I'll head out and see what I can find!"translation="D’acord! Doncs me’n vaig a mirar què hi trobo!"/>
<dialoguespeaker="purple"english="I'll be right here if you need any help!"translation="Si necessites ajuda, seré aquí!"/>
</cutscene>
<cutsceneid="bigopenworldskip"explanation="">
<dialoguespeaker="purple"english="I'll be right here if you need any help!"translation="Si necessites ajuda, seré aquí!"/>
</cutscene>
<cutsceneid="talkpurple_intro"explanation="">
<dialoguespeaker="player"english="I'm feeling a bit overwhelmed, Doctor."translation="Tot això m’aclapara una mica, doctora."/>
<dialoguespeaker="player"english="Where do I begin?"translation="Per on començo?"/>
<dialoguespeaker="purple"english="Remember that you can press {b_map} to check where you are on the map!"translation="Recorda que pots prémer {b_map} per a veure on ets del mapa!"buttons="1"/>
<dialoguespeaker="purple"english="Look for areas where the rest of the crew might be..."translation="Cerca zones on pugui haver-hi altres tripulants..."/>
<dialoguespeaker="purple"english="If you get lost, you can get back to the ship from any teleporter."translation="Si et perds, pots tornar a la nau a partir de qualsevol teletransportador."/>
<dialoguespeaker="purple"english="And don't worry! We'll find everyone!"translation="I no pateixis! Segur que trobarem tothom!"/>
<dialoguespeaker="purple"english="Everything will be ok!"translation="Tot anirà bé!"/>
</cutscene>
<cutsceneid="talkpurple_3"explanation="only one player string is shown">
<dialoguespeaker="purple"english="Are you doing ok, Captain?"translation="Estàs bé, cap?"/>
<dialoguespeaker="player"english="I'm worried about Victoria, Doctor!"translation="Em preocupa la Victòria, doctora!"/>
<dialoguespeaker="player"english="I'm worried about Vitellary, Doctor!"translation="Em preocupa en Vitel·lí, doctora!"/>
<dialoguespeaker="player"english="I'm worried about Verdigris, Doctor!"translation="Em preocupa en Verdet, doctora!"/>
<dialoguespeaker="player"english="I'm worried about Vermilion, Doctor!"translation="Em preocupa en Vermelló, doctora!"/>
<dialoguespeaker="player"english="I'm worried about you, Doctor!"translation="Em preocupes tu, doctora!"/>
<dialoguespeaker="purple"english="Oh - well, don't worry, they'll show up!"translation="Ah, no pateixis, segur que apareixerà!"/>
<dialoguespeaker="purple"english="Here! Have a lollipop!"translation="Té, una piruleta!"/>
</cutscene>
<cutsceneid="trinketcollector"explanation="if no trinkets found yet">
<dialoguespeaker="cyan"english="This seems like a good place to store anything I find out there..."translation="Això sembla un bon lloc on emmagatzemar les coses que trobi allà fora..."/>
<dialoguespeaker="cyan"english="Victoria loves to study the interesting things we find on our adventures!"translation="La Victòria gaudeix estudiant les coses interessants que trobem durant les nostres aventures!"/>
</cutscene>
<cutsceneid="newtrinketcollector"explanation="if at least one trinket found">
<dialoguespeaker="cyan"english="This seems like a good place to store those shiny things."translation="Això sembla un bon lloc on emmagatzemar aquelles coses brillants..."/>
<dialoguespeaker="cyan"english="Victoria loves to study the interesting things we find on our adventures!"translation="La Victòria gaudeix estudiant les coses interessants que trobem durant les nostres aventures!"/>
</cutscene>
<cutsceneid="new2trinketcollector"explanation="">
<dialoguespeaker="cyan"english="I hope she's ok..."translation="Espero que estigui bé..."/>
</cutscene>
<cutsceneid="rescuegreen"explanation="nuance: `she's BACK on the ship` means more `she's home` and not `she has returned to the ship`. She has never left the ship">
<dialoguespeaker="green"english="Captain! I've been so worried!"translation="Cap! Estava molt preocupat!"/>
<dialoguespeaker="player"english="Chief Verdigris! You're ok!"translation="Enginyer en cap Verdet! Estàs bé!"/>
<dialoguespeaker="green"english="I've been trying to get out, but I keep going around in circles..."translation="He mirat de sortir d’aquí, però no deixo de fer voltes..."/>
<dialoguespeaker="player"english="I've come from the ship. I'm here to teleport you back to it."translation="Vinc de la nau. Sóc aquí per a teletransportar-t’hi."/>
<dialoguespeaker="green"english="Is everyone else alright? Is Violet..."translation="La resta estan bé? I la Violeta..."/>
<dialoguespeaker="player"english="She's fine - she's back on the ship!"translation="Està bé. És a la nau!"/>
<dialoguespeaker="green"english="Oh! Great - Let's get going, then!"translation="Ah, fantàstic! Som-hi, doncs!"/>
</cutscene>
<cutsceneid="rescueblue"explanation="">
<dialoguespeaker="blue"english="Oh no! Captain! Are you stuck here too?"translation="Ostres, no! Cap! Tu tampoc no pots sortir d’aquí?"/>
<dialoguespeaker="player"english="It's ok - I'm here to rescue you!"translation="No pateixis, he vingut|a rescatar-te!"/>
<dialoguespeaker="player"english="Let me explain everything..."translation="Deixa’m explicar-t’ho tot..."/>
<dialoguespeaker="blue"english="What? I didn't understand any of that!"translation="Què? No he entès res de res!"/>
<dialoguespeaker="player"english="Oh... well, don't worry."translation="Ah... Bé, doncs no pateixis."/>
<dialoguespeaker="player"english="Follow me! Everything will be alright!"translation="Segueix-me! Tot anirà bé!"/>
<dialoguespeaker="red"english="Am I ever glad to see you! I thought I was the only one to escape the ship..."translation="M’alegro molt de veure’t! Pensava que era l’únic que s’havia escapat de la nau..."/>
<dialoguespeaker="player"english="Vermilion! I knew you'd be ok!"translation="Vermelló! Sabia que estaries bé!"/>
<dialoguespeaker="red"english="So, what's the situation?"translation="I doncs, quina és la situació?"/>
<dialoguespeaker="red"english="I see! Well, we'd better get back then."translation="És clar! D’acord, doncs millor que tornem."/>
<dialoguespeaker="red"english="There's a teleporter in the next room."translation="Hi ha un teletransportador a la sala del costat."/>
</cutscene>
<cutsceneid="rescueyellow"explanation="">
<dialoguespeaker="yellow"english="Ah, Viridian! You got off the ship alright too?"translation="Ah, Viridis! Tu també vas sortir de la nau sense fer-te mal, oi?"/>
<dialoguespeaker="player"english="It's good to see you're alright, Professor!"translation="M’alegro de veure que estàs bé, professor!"/>
<dialoguespeaker="yellow"english="Is the ship ok?"translation="La nau està bé?"/>
<dialoguespeaker="player"english="It's badly damaged, but Violet's been working on fixing it."translation="Està força malmesa, però la Violeta|treballa per a reparar-la."/>
<dialoguespeaker="player"english="We could really use your help..."translation="La teva ajuda ens vindria molt bé..."/>
<dialoguespeaker="yellow"english="Ah, of course!"translation="Ah, és clar!"/>
<dialoguespeaker="yellow"english="The background interference in this dimension prevented the ship from finding a teleporter when we crashed!"translation="Les interferències del rerefons d’aquesta dimensió van impedir que la nau trobés un teletransportador abans que ens estavelléssim!"/>
<dialoguespeaker="yellow"english="We've all been teleported to different locations!"translation="Per això hem acabat teletransportats|a diferents llocs!"/>
<dialoguespeaker="player"english="Er, that sounds about right!"translation="Eh... És clar, té sentit!"/>
<dialoguespeaker="yellow"english="Let's get back to the ship, then!"translation="Tornem a la nau, doncs!"/>
<dialoguespeaker="player"english="Something's gone wrong... We should look for a way back!"translation="Alguna cosa no ha anat bé... Hem de cercar un camí de tornada!"/>
</cutscene>
<cutsceneid="int1blue_2"explanation="">
<dialoguespeaker="player"english="Follow me! I'll help you!"translation="Segueix-me! T’ajudaré!"/>
<dialoguespeaker="blue"english="Promise you won't leave without me!"translation="Promet-me que no te n’aniràs sense mi!"/>
<dialoguespeaker="player"english="I promise! Don't worry!"translation="T’ho prometo! No pateixis!"/>
</cutscene>
<cutsceneid="int1blue_3"explanation="">
<dialoguespeaker="player"english="Are you ok down there, Doctor?"translation="Va tot bé allà baix, doctora?"/>
<dialoguespeaker="blue"english="I wanna go home!"translation="Me’n vull anar a casa!"/>
<dialoguespeaker="blue"english="Where are we? How did we even get here?"translation="On som? Com se suposa que hi hem arribat?"/>
<dialoguespeaker="player"english="Well, Violet did say that the interference in the dimension we crashed in was causing problems with the teleporters..."translation="Doncs... la Violeta deia que les interferències de la dimensió on ens hem estavellat causaven problemes amb els teletransportadors..."/>
<dialoguespeaker="player"english="I guess something went wrong..."translation="Suposo que alguna cosa ha anat malament..."/>
<dialoguespeaker="player"english="But if we can find another teleporter, I think we can get back to the ship!"translation="Però si trobem un altre teletransportador, suposo que podrem tornar a la nau!"/>
<dialoguespeaker="blue"english="Captain! Captain! Wait for me!"translation="Cap! Cap! Espera’m!"/>
<dialoguespeaker="blue"english="Please don't leave me behind! I don't mean to be a burden!"translation="No em deixis enrere!|No vull ser cap càrrega!"/>
<dialoguespeaker="player"english="Oh... don't worry Victoria, I'll look after you!"translation="Ah... No pateixis, Victòria, tindré cura de tu!"/>
</cutscene>
<cutsceneid="int1blue_5"explanation="">
<dialoguespeaker="blue"english="We're never going to get out of here, are we?"translation="No sortirem mai d’aquí, oi que no?"/>
<dialoguespeaker="player"english="I.. I don't know..."translation="N... no ho sé..."/>
<dialoguespeaker="player"english="I don't know where we are or how we're going to get out..."translation="No sé on som ni com n’hem de sortir..."/>
</cutscene>
<cutsceneid="int1blue_6"explanation="">
<dialoguespeaker="blue"english="We're going to be lost forever!"translation="Acabarem perduts per sempre més!"/>
<dialoguespeaker="player"english="Ok, come on... Things aren't that bad."translation="Au, va... Les coses no estan tan malament."/>
<dialoguespeaker="player"english="I have a feeling that we're nearly home!"translation="Tinc la sensació que som a prop de casa!"/>
<dialoguespeaker="player"english="We can't be too far from another teleporter!"translation="No podem ser gaire lluny d’un altre teletransportador!"/>
<dialoguespeaker="blue"english="I hope you're right, captain..."translation="Espero que tinguis raó, cap..."/>
</cutscene>
<cutsceneid="int1blue_7"explanation="">
<dialoguespeaker="blue"english="Captain! You were right! It's a teleporter!"translation="Cap! Tenies raó! Hi ha un teletransportador!"/>
<dialoguespeaker="player"english="Phew! You had me worried for a while there... I thought we were never going to find one."translation="Buf! Ja patia, la veritat... Pensava que no en trobaríem mai cap."/>
<dialoguespeaker="blue"english="What? Really?"translation="Què? De debò?"/>
<dialoguespeaker="player"english="Anyway, let's go back to the ship."translation="En fi, tornem a la nau."/>
</cutscene>
<cutsceneid="int1green_1"explanation="">
<dialoguespeaker="green"english="Huh? This isn't the ship..."translation="Eh? Això no és la nau..."/>
<dialoguespeaker="green"english="Captain! What's going on?"translation="Cap! Què passa?"/>
<dialoguespeaker="player"english="I... I don't know!"translation="N... no ho sé!"/>
<dialoguespeaker="player"english="Where are we?"translation="On som?"/>
<dialoguespeaker="green"english="Uh oh, this isn't good... Something must have gone wrong with the teleporter!"translation="Caram, això no va bé...|El teletransportador devia tenir algun problema!"/>
<dialoguespeaker="player"english="Ok... no need to panic!"translation="D’acord... Mantinguem la calma!"/>
<dialoguespeaker="player"english="Let's look for another teleporter!"translation="Cerquem un altre teletransportador!"/>
</cutscene>
<cutsceneid="int1green_2"explanation="">
<dialoguespeaker="player"english="Let's go this way!"translation="Anem cap allà!"/>
<dialoguespeaker="green"english="After you, Captain!"translation="Després de tu, cap!"/>
</cutscene>
<cutsceneid="int1green_3"explanation="just like in rescuegreen, `Violet is back on the ship` does not mean she was ever off the ship and has *returned* to it. Or maybe Verdigris thinks Violet was warped off the ship as well just like him and she *has* made her way back to the ship?">
<dialoguespeaker="green"english="So Violet's back on the ship? She's really ok?"translation="Així, la Violeta és a la nau? De debò que està bé?"/>
<dialoguespeaker="player"english="She's fine! She helped me find my way back!"translation="I tant! M’ha ajudat a trobar|el camí de tornada!"/>
<dialoguespeaker="green"english="Oh, phew! I was worried about her."translation="Ah, uf! Patia per ella."/>
<dialoguespeaker="green"english="Captain, I have a secret..."translation="Cap, tinc un secret..."/>
<dialoguespeaker="green"english="I really like Violet!"translation="M’agrada molt la Violeta!"/>
<dialoguespeaker="player"english="Is that so?"translation="De debò?"/>
<dialoguespeaker="green"english="Please promise you won't tell her!"translation="Promet-me que no li ho diràs!"/>
<dialoguespeaker="player"english="Are you doing ok?"translation="Va tot bé?"/>
<dialoguespeaker="green"english="I think so! I really hope we can find a way back to the ship..."translation="Diria que sí! Espero que trobem|un camí cap a la nau..."/>
</cutscene>
<cutsceneid="int1green_5"explanation="">
<dialoguespeaker="green"english="So, about Violet..."translation="Daixò... Pel que fa a la Violeta..."/>
<dialoguespeaker="player"english="So, do you think you'll be able to fix the ship?"translation="I què, creus que podràs|reparar la nau?"/>
<dialoguespeaker="green"english="Depends on how bad it is... I think so, though!"translation="Depèn de com estigui de malament...|Però penso que sí!"/>
<dialoguespeaker="green"english="It's not very hard, really. The basic dimensional warping engine design is pretty simple, and if we can get that working we shouldn't have any trouble getting home."translation="No és tan difícil, en realitat. El disseny bàsic del motor de salt dimensional és força simple,|i si aconseguim que això funcioni, no hauríem de tenir problemes per a arribar a casa."/>
<dialoguespeaker="green"english="Finally! A teleporter!"translation="Per fi! Un teletransportador!"/>
<dialoguespeaker="green"english="I was getting worried we wouldn't find one..."translation="Ja començava a pensar que no en trobaríem cap..."/>
<dialoguespeaker="player"english="Let's head back to the ship!"translation="Tornem a la nau!"/>
</cutscene>
<cutsceneid="int1red_1"explanation="">
<dialoguespeaker="red"english="Wow! Where are we?"translation="Bufa! On som?"/>
<dialoguespeaker="player"english="This... isn't right... Something must have gone wrong with the teleporter!"translation="Això... no va bé... El teletransportador devia tenir algun problema!"/>
<dialoguespeaker="red"english="Oh well... We can work it out when we get back to the ship!"translation="Què hi farem... Ja ho investigarem quan tornem a la nau!"/>
<dialoguespeaker="red"english="Let's go exploring!"translation="Som-hi, a explorar!"/>
<dialoguespeaker="red"english="Hey Viridian... how did the crash happen, exactly?"translation="Ei, Viridis... Com és que ens vam estavellar, exactament?"/>
<dialoguespeaker="player"english="Oh, I don't really know - some sort of interference..."translation="Ah, doncs no ho sé del cert... Només sé que per les interferències..."/>
<dialoguespeaker="player"english="...or something sciencey like that. It's not really my area."translation="O per alguna cosa científica així... No és el meu fort, aquest tema."/>
<dialoguespeaker="red"english="Ah! Well, do you think we'll be able to fix the ship and go home?"translation="Ah! Bé, i creus que podrem reparar la nau i tornar a casa?"/>
<dialoguespeaker="player"english="Of course! Everything will be ok!"translation="És clar! Tot anirà bé!"/>
<dialoguespeaker="red"english="Hi again! You doing ok?"translation="Ep! Va tot bé?"/>
<dialoguespeaker="player"english="I think so! But I really want to get back to the ship..."translation="Crec que sí! Però tinc moltes ganes de tornar a la nau..."/>
<dialoguespeaker="red"english="We'll be ok! If we can find a teleporter somewhere we should be able to get back!"translation="Ens en sortirem! Si aconseguim trobar un teletransportador en algun lloc, hauríem de poder tornar-hi!"/>
</cutscene>
<cutsceneid="int1red_5"explanation="">
<dialoguespeaker="red"english="Are we there yet?"translation="Falta gaire?"/>
<dialoguespeaker="player"english="We're getting closer, I think..."translation="Ens hi estem acostant, em sembla..."/>
<dialoguespeaker="player"english="I wonder where we are, anyway?"translation="On devem ser, a tot això?"/>
<dialoguespeaker="player"english="This seems different from that dimension we crashed in, somehow..."translation="No sé per què, però aquest lloc sembla diferent de la dimensió on ens vam estavellar..."/>
<dialoguespeaker="red"english="I dunno... But we must be close to a teleporter by now..."translation="No sé... Però ara ja devem ser a prop d’un teletransportador..."/>
</cutscene>
<cutsceneid="int1red_7"explanation="">
<dialoguespeaker="player"english="We're there!"translation="Ja hi som!"/>
<dialoguespeaker="red"english="See? I told you! Let's get back to the ship!"translation="Ho veus? T’ho he dit! Vinga, tornem a la nau!"/>
</cutscene>
<cutsceneid="int1yellow_1"explanation="">
<dialoguespeaker="yellow"english="Oooh! This is interesting..."translation="Oooh! Que interessant..."/>
<dialoguespeaker="yellow"english="Captain! Have you been here before?"translation="Cap! Has estat mai aquí?"/>
<dialoguespeaker="player"english="What? Where are we?"translation="Què? On som?"/>
<dialoguespeaker="yellow"english="I suspect something deflected our teleporter transmission! This is somewhere new..."translation="Em sembla que alguna cosa ha desviat la transmissió del teletransportador! Aquest lloc és nou..."/>
<dialoguespeaker="player"english="We should try to find a teleporter and get back to the ship..."translation="Hauríem de mirar de trobar un teletransportador i tornar a la nau..."/>
<dialoguespeaker="player"english="What do you make of all this, Professor?"translation="Què en penses, de|tot això, professor?"/>
<dialoguespeaker="yellow"english="I'm guessing this dimension has something to do with the interference that caused us to crash!"translation="Suposo que aquesta dimensió té|alguna cosa a veure amb les interferències que han causat que ens estavelléssim!"/>
<dialoguespeaker="yellow"english="Maybe we'll find the cause of it here?"translation="Potser en trobarem la causa aquí?"/>
<dialoguespeaker="player"english="Oh wow! Really?"translation="Ah, ostres! De debò?"/>
<dialoguespeaker="yellow"english="Well, it's just a guess. I'll need to get back to the ship before I can do any real tests..."translation="Bé, només és una suposició. Necessito tornar a la nau abans de poder fer cap experiment real..."/>
</cutscene>
<cutsceneid="int1yellow_4"explanation="the split paths merge, and Vitellary sees a checkpoint">
<dialoguespeaker="yellow"english="Ohh! What was that?"translation="Oooh! Què és, això?"/>
<dialoguespeaker="player"english="What was what?"translation="Què és, això?"/>
<dialoguespeaker="yellow"english="That big... C thing! I wonder what it does?"translation="Aquesta cosa grossa... amb una C! Què deu fer?"/>
<dialoguespeaker="player"english="Em... I don't really know how to answer that question..."translation="Eh... No sé com respondre a la teva qüestió..."/>
<dialoguespeaker="player"english="It's probably best not to acknowledge that it's there at all."translation="Segurament és millor que fem veure que no existeix."/>
<dialoguespeaker="yellow"english="Maybe we should take it back to the ship to study it?"translation="Ens la podríem emportar a la nau i estudiar-la!"/>
<dialoguespeaker="player"english="We really shouldn't think about it too much... Let's keep moving!"translation="No hi hauríem de pensar pas gaire... Vinga, som-hi!"/>
</cutscene>
<cutsceneid="int1yellow_5"explanation="">
<dialoguespeaker="yellow"english="You know, there's something really odd about this dimension..."translation="Saps? Hi ha una cosa molt estranya en aquesta dimensió..."/>
<dialoguespeaker="yellow"english="We shouldn't really be able to move between dimensions with a regular teleporter..."translation="No ens hauríem de poder moure entre dimensions amb un teletransportador normal..."/>
<dialoguespeaker="yellow"english="Maybe this isn't a proper dimension at all?"translation="Potser no és una dimensió com a tal?"/>
<dialoguespeaker="yellow"english="Maybe it's some kind of polar dimension? Something artificially created for some reason?"translation="Potser és alguna mena de dimensió polar? Alguna cosa creada artificialment per algun motiu..."/>
<dialoguespeaker="yellow"english="I can't wait to get back to the ship. I have a lot of tests to run!"translation="Em moro de ganes de tornar a la nau. Hi he de fer un munt d’experiments!"/>
</cutscene>
<cutsceneid="int1yellow_6"explanation="">
<dialoguespeaker="yellow"english="I wonder if there's anything else in this dimension worth exploring?"translation="Hi deu haver alguna altra cosa d’aquesta dimensió que pagui la pena d’explorar?"/>
<dialoguespeaker="player"english="Maybe... but we should probably just focus on finding the rest of the crew for now..."translation="Potser... Però segurament només ens hauríem de centrar a trobar la resta de la tripulació, ara mateix..."/>
<dialoguespeaker="purple"english="... I hope Verdigris is alright."translation="Espero que en Verdet estigui bé..."/>
<dialoguespeaker="purple"english="If you can find him, he'd be a big help fixing the ship!"translation="Si aconseguissis trobar-lo, seria de gran ajuda a l’hora|de reparar la nau!"/>
</cutscene>
<cutsceneid="talkpurple_2"explanation="">
<dialoguespeaker="purple"english="Chief Verdigris is so brave and ever so smart!"translation="L’enginyer en cap Verdet és valent i intel·ligent alhora!"/>
</cutscene>
<cutsceneid="talkpurple_4"explanation="">
<dialoguespeaker="purple"english="Welcome back, Captain!"translation="Tornes a ser aquí, cap!"/>
<dialoguespeaker="purple"english="I think Victoria is quite happy to be back on the ship."translation="Em sembla que la Victòria està força contenta de tornar a ser a la nau."/>
<dialoguespeaker="purple"english="She really doesn't like adventuring. She gets very homesick!"translation="No li agrada gens anar d’aventures. S’enyora molt fàcilment!"/>
</cutscene>
<cutsceneid="talkpurple_5"explanation="only one of the last 6 strings is shown">
<dialoguespeaker="purple"english="Vermilion called in to say hello!"translation="En Vermelló t’envia salutacions!"/>
<dialoguespeaker="purple"english="He's really looking forward to helping you find the rest of the crew!"translation="Té moltes ganes d’ajudar-te a trobar la resta de la tripulació!"/>
<dialoguespeaker="purple"english="He's really looking forward to helping you find Victoria!"translation="Té moltes ganes d’ajudar-te a trobar la Victòria!"/>
<dialoguespeaker="purple"english="He's really looking forward to helping you find Vitellary!"translation="Té moltes ganes d’ajudar-te a trobar en Vitel·lí!"/>
<dialoguespeaker="purple"english="He's really looking forward to helping you find Verdigris!"translation="Té moltes ganes d’ajudar-te a trobar en Verdet!"/>
<dialoguespeaker="purple"english="He's really looking forward to helping you find Vermilion!"translation="Té moltes ganes d’ajudar-te a trobar en Vermelló!"/>
<dialoguespeaker="purple"english="He's really looking forward to helping you find you!"translation="Té moltes ganes d’ajudar-te|a trobar-te!"/>
</cutscene>
<cutsceneid="talkpurple_6"explanation="">
<dialoguespeaker="purple"english="Captain! You found Verdigris!"translation="Cap! Has trobat en Verdet!"/>
<dialoguespeaker="purple"english="Thank you so much!"translation="Moltes gràcies!"/>
</cutscene>
<cutsceneid="talkpurple_7"explanation="">
<dialoguespeaker="purple"english="I'm glad Professor Vitellary is ok!"translation="M’alegro que el professor Vitel·lí estigui bé!"/>
<dialoguespeaker="purple"english="He had lots of questions for me about this dimension."translation="Em volia fer un munt de qüestions sobre aquesta dimensió."/>
<dialoguespeaker="purple"english="He's already gotten to work with his research!"translation="Ja s’ha posat a investigar!"/>
<dialoguespeaker="player"english="Doctor, something strange happened when we teleported back to the ship..."translation="Doctora, ha passat una cosa estranya quan ens hem teletransportat a la nau..."/>
<dialoguespeaker="player"english="We got lost in another dimension!"translation="Ens hem perdut en una altra dimensió!"/>
<dialoguespeaker="purple"english="Maybe that dimension has something to do with the interference that caused us to crash here?"translation="Potser aquella dimensió té alguna cosa a veure amb les interferències que han causat que ens estavelléssim aquí?"/>
<dialoguespeaker="purple"english="I'll look into it..."translation="Ho investigaré..."/>
<dialoguespeaker="player"english="Doctor! Doctor! It happened again!"translation="Doctora, doctora! Ha tornat a passar!"/>
<dialoguespeaker="player"english="The teleporter brought us to that weird dimension..."translation="El teletransportador ens ha portat a aquella dimensió estranya..."/>
<dialoguespeaker="purple"english="Hmm, there's definitely something strange happening..."translation="Hum... Sens dubte, passa alguna cosa estranya..."/>
<dialoguespeaker="purple"english="If only we could find the source of that interference!"translation="Tant de bo trobéssim l’origen de les interferències!"/>
<dialoguespeaker="player"english="Doctor, something strange has been happening when we teleport back to the ship..."translation="Doctora, quan ens teletransportem a la nau, passen coses estranyes..."/>
<dialoguespeaker="player"english="We keep getting brought to another weird dimension!"translation="Sempre ens acaba portant a una altra dimensió estranya!"/>
<dialoguespeaker="purple"english="Maybe that dimension has something to do with the interference that caused us to crash here?"translation="Potser aquella dimensió té alguna cosa a veure amb les interferències que han causat que ens estavelléssim aquí?"/>
<dialoguespeaker="purple"english="Hmm, there's definitely something strange happening..."translation="Hum... Sens dubte, passa alguna cosa estranya..."/>
<dialoguespeaker="purple"english="If only we could find the source of that interference!"translation="Tant de bo trobéssim l’origen de les interferències!"/>
</cutscene>
<cutsceneid="talkpurple_8"explanation="">
<dialoguespeaker="purple"english="Hey Captain! Now that you've turned off the source of the interference, we can warp everyone back to the ship instantly, if we need to!"translation="Ei, cap! Ara que has desactivat l’origen de les interferències, podem teletransportar tothom a la nau instantàniament, si cal!"/>
<dialoguespeaker="purple"english="Any time you want to come back to the ship, just select the new SHIP option in your menu!"translation="Si en algun moment vols tornar a la nau, selecciona la nova opció NAU al menú!"/>
</cutscene>
<cutsceneid="talkgreen_1"explanation="">
<dialoguespeaker="green"english="I'm an engineer!"translation="Sóc enginyer!"/>
</cutscene>
<cutsceneid="talkgreen_2"explanation="">
<dialoguespeaker="green"english="I think I can get this ship moving again, but it's going to take a while..."translation="Em sembla que podré aconseguir que la nau es torni a moure, però em costarà una bona estona..."/>
</cutscene>
<cutsceneid="talkgreen_3"explanation="">
<dialoguespeaker="green"english="Victoria mentioned something about a lab? I wonder if she found anything down there?"translation="La Victòria parlava d’un laboratori o una cosa així? Potser hi ha trobat alguna cosa, allà baix?"/>
</cutscene>
<cutsceneid="talkgreen_4"explanation="">
<dialoguespeaker="green"english="Vermilion's back! Yey!"translation="En Vermelló ha tornat! Visca!"/>
</cutscene>
<cutsceneid="talkgreen_5"explanation="">
<dialoguespeaker="green"english="The Professor had lots of questions about this dimension for me..."translation="El professor em volia fer un munt de qüestions sobre aquesta dimensió..."/>
<dialoguespeaker="green"english="We still don't really know that much, though."translation="Però encara no en sabem gaire cosa."/>
<dialoguespeaker="green"english="Until we work out what's causing that interference, we can't go anywhere."translation="Fins que no descobrim què causa les interferències, no podrem anar enlloc."/>
</cutscene>
<cutsceneid="talkgreen_6"explanation="">
<dialoguespeaker="green"english="I'm so glad that Violet's alright!"translation="M’alegro molt que la Violeta estigui bé!"/>
</cutscene>
<cutsceneid="talkgreen_7"explanation="">
<dialoguespeaker="green"english="That other dimension we ended up in must be related to this one, somehow..."translation="L’altra dimensió on havíem anat a parar deu estar relacionada amb aquesta d’alguna manera..."/>
</cutscene>
<cutsceneid="talkgreen_8"explanation="">
<dialoguespeaker="green"english="The antenna's broken! This is going to be very hard to fix..."translation="L’antena està trencada! Això costarà molt de reparar..."/>
</cutscene>
<cutsceneid="talkgreen_9"explanation="">
<dialoguespeaker="green"english="It looks like we were warped into solid rock when we crashed!"translation="Sembla com si ens haguéssim teletransportat enmig de roca sòlida en estavellar-nos!"/>
<dialoguespeaker="green"english="Hmm. It's going to be hard to separate from this..."translation="Hum... Costarà separar-nos-en..."/>
</cutscene>
<cutsceneid="talkgreen_10"explanation="">
<dialoguespeaker="green"english="The ship's all fixed up. We can leave at a moment's notice!"translation="La nau ja està tota reparada. Podem anar-nos-en en un tres i no res!"/>
<dialoguespeaker="red"english="We'll find a way out of here!"translation="Trobarem una manera de sortir d’aquí!"/>
</cutscene>
<cutsceneid="talkred_2"explanation="">
<dialoguespeaker="red"english="I hope Victoria is ok..."translation="Espero que la Victòria estigui bé..."/>
<dialoguespeaker="red"english="She doesn't handle surprises very well..."translation="No li agraden gaire les sorpreses..."/>
</cutscene>
<cutsceneid="talkred_3"explanation="">
<dialoguespeaker="red"english="I don't know how we're going to get this ship working again!"translation="No sé pas com farem que la nau torni a funcionar!"/>
<dialoguespeaker="red"english="Chief Verdigris would know what to do..."translation="L’enginyer en cap Verdet sabria què fer..."/>
</cutscene>
<cutsceneid="talkred_4"explanation="">
<dialoguespeaker="red"english="I wonder what caused the ship to crash here?"translation="Què devia fer que la nau s’estavellés aquí?"/>
<dialoguespeaker="red"english="It's the shame the Professor isn't here, huh? I'm sure he could work it out!"translation="És una llàstima que el professor no hi sigui, eh? Segur que ell ho esbrinaria!"/>
</cutscene>
<cutsceneid="talkred_5"explanation="">
<dialoguespeaker="red"english="It's great to be back!"translation="És genial tornar a ser aquí!"/>
<dialoguespeaker="red"english="I can't wait to help you find the rest of the crew!"translation="Em moro de ganes d’ajudar-te a trobar la resta de tripulants!"/>
<dialoguespeaker="red"english="It'll be like old times, huh, Captain?"translation="Serà com als vells temps, oi, cap?"/>
</cutscene>
<cutsceneid="talkred_6"explanation="">
<dialoguespeaker="red"english="It's good to have Victoria back with us."translation="És genial que la Victòria torni a ser amb nosaltres."/>
<dialoguespeaker="red"english="She really seems happy to get back to work in her lab!"translation="Sembla molt feliç de poder tornar a treballar al seu laboratori!"/>
</cutscene>
<cutsceneid="talkred_7"explanation="">
<dialoguespeaker="red"english="I think I saw Verdigris working on the outside of the ship!"translation="Em sembla que he vist en Verdet treballant a la part exterior de la nau!"/>
</cutscene>
<cutsceneid="talkred_8"explanation="">
<dialoguespeaker="red"english="You found Professor Vitellary! All right!"translation="Has trobat el professor Vitel·lí! Molt bé!"/>
<dialoguespeaker="red"english="We'll have this interference thing worked out in no time now!"translation="Ara resoldre el tema de les interferències serà bufar i fer ampolles!"/>
</cutscene>
<cutsceneid="talkred_9"explanation="">
<dialoguespeaker="red"english="That other dimension was really strange, wasn't it?"translation="Aquella altra dimensió era ben estranya, eh?"/>
<dialoguespeaker="red"english="I wonder what caused the teleporter to send us there?"translation="Per què el teletransportador ens va enviar allà?"/>
<dialoguespeaker="red"english="I found something interesting around here - the same warp signature I saw when I landed!"translation="En aquesta zona he trobat una cosa interessant: les mateixes formes derivades d’un teletransport que vaig veure quan vaig aterrar!"/>
<dialoguespeaker="red"english="Someone from the ship must be nearby..."translation="Hi ha d’haver algú de la nau a prop..."/>
</cutscene>
<cutsceneid="talkred_13"explanation="">
<dialoguespeaker="red"english="This dimension is pretty exciting, isn't it?"translation="Aquesta dimensió és força emocionant, oi?"/>
<dialoguespeaker="red"english="I wonder what we'll find?"translation="Què hi trobarem?"/>
</cutscene>
<cutsceneid="talkblue_1"explanation="">
<dialoguespeaker="blue"english="Any signs of Professor Vitellary?"translation="Hi ha cap senyal del professor Vitel·lí?"/>
<dialoguespeaker="player"english="Sorry, not yet..."translation="Em sap greu, però de moment, no..."/>
<dialoguespeaker="blue"english="I hope he's ok..."translation="Espero que estigui bé..."/>
</cutscene>
<cutsceneid="talkblue_2"explanation="">
<dialoguespeaker="blue"english="Thanks so much for saving me, Captain!"translation="Moltes gràcies per salvar-me, cap!"/>
</cutscene>
<cutsceneid="talkblue_3"explanation="">
<dialoguespeaker="blue"english="I'm so glad to be back!"translation="M’alegro molt de tornar a ser aquí!"/>
<dialoguespeaker="blue"english="That lab was so dark and scary! I didn't like it at all..."translation="Aquell laboratori era fosc i feia por! No m’agradava gens..."/>
</cutscene>
<cutsceneid="talkblue_4"explanation="">
<dialoguespeaker="blue"english="Vitellary's back? I knew you'd find him!"translation="En Vitel·lí torna a ser aquí? Sabia que el trobaries!"/>
<dialoguespeaker="blue"english="I mean, I admit I was very worried that you wouldn't..."translation="Tot i que he d’admetre que tenia molta por que no el trobessis..."/>
<dialoguespeaker="blue"english="or that something might have happened to him..."translation="O que li hagués passat|alguna cosa..."/>
<dialoguespeaker="player"english="Doctor Victoria? He's ok!"translation="Doctora Victòria... Que està bé!"/>
<dialoguespeaker="blue"english="Oh! Sorry! I was just thinking about what if he wasn't?"translation="Ah, perdona! És que pensava en què passaria si no ho estigués!"/>
<dialoguespeaker="blue"english="Though I was very worried..."translation="Tot i que patia molt..."/>
</cutscene>
<cutsceneid="talkblue_7"explanation="">
<dialoguespeaker="blue"english="Why did the teleporter send us to that scary dimension?"translation="Per què el teletransportador ens ha enviat a aquella dimensió tan espantosa?"/>
<dialoguespeaker="blue"english="What happened?"translation="Què ha passat?"/>
<dialoguespeaker="player"english="I don't know, Doctor..."translation="No ho sé, doctora..."/>
<dialoguespeaker="blue"english="Are you going to try and find the rest of these shiny things?"translation="Miraràs de trobar la resta d’aquestes coses brillants?"/>
</cutscene>
<cutsceneid="talkblue_trinket1"explanation="">
<dialoguespeaker="blue"english="Hey Captain, I found this in that lab..."translation="Ei, cap, he trobat això en aquell laboratori..."/>
<dialoguespeaker="blue"english="Any idea what it does?"translation="Tens cap idea de per a què serveix?"/>
<dialoguespeaker="player"english="Sorry, I don't know!"translation="Em sap greu, però no ho sé!"/>
<dialoguespeaker="player"english="Maybe something will happen if we find them all?"translation="Potser passarà alguna cosa si els trobem tots?"/>
</cutscene>
<cutsceneid="talkblue_trinket2"explanation="">
<dialoguespeaker="blue"english="Captain! Come have a look at what I've been working on!"translation="Cap! Mira en què he estat treballant!"/>
<dialoguespeaker="blue"english="It looks like these shiny things are giving off a strange energy reading!"translation="Sembla que aquelles coses brillants tenen unes lectures d’energia estranyes!"/>
<dialoguespeaker="blue"english="So I analysed it..."translation="Així que ho he analitzat..."/>
</cutscene>
<cutsceneid="talkblue_trinket3"explanation="">
<dialoguespeaker="blue"english="Captain! Come have a look at what I've been working on!"translation="Cap! Mira en què he estat treballant!"/>
<dialoguespeaker="blue"english="I found this in that lab..."translation="He trobat això en aquell laboratori..."/>
<dialoguespeaker="blue"english="It seemed to be giving off a weird energy reading..."translation="Tenia unes lectures d’energia estranyes..."/>
<dialoguespeaker="blue"english="So I analysed it..."translation="Així que ho he analitzat..."/>
</cutscene>
<cutsceneid="talkblue_trinket4"explanation="">
<dialoguespeaker="blue"english="...and I was able to find more of them with the ship's scanner!"translation="...i n’he descobert més amb l’escàner de la nau!"/>
<dialoguespeaker="blue"english="If you get a chance, it might be worth finding the rest of them!"translation="Si t’és possible, potser estaria|bé trobar-ne la resta!"/>
<dialoguespeaker="blue"english="Don't put yourself in any danger, though!"translation="Però no et posis en perill, eh?"/>
</cutscene>
<cutsceneid="talkblue_trinket5"explanation="">
<dialoguespeaker="blue"english="...but it looks like you've already found all of them in this dimension!"translation="...però sembla que ja les has trobades totes en aquesta dimensió!"/>
<dialoguespeaker="player"english="Oh? Really?"translation="Ah, sí? De debò?"/>
<dialoguespeaker="blue"english="Yeah, well done! That can't have been easy!"translation="Sí, molt ben fet! Segur que no ha estat fàcil!"/>
</cutscene>
<cutsceneid="talkblue_trinket6"explanation="">
<dialoguespeaker="blue"english="...and they're related. They're all a part of something bigger!"translation="...i estan relacionades. Són part d’una cosa més grossa!"/>
<dialoguespeaker="player"english="Oh? Really?"translation="Ah, sí? De debò?"/>
<dialoguespeaker="blue"english="Yeah! There seem to be twenty variations of the fundamental energy signature..."translation="Sí! Sembla que hi ha vint variacions de la signatura d’energia fonamental..."/>
<dialoguespeaker="blue"english="Does that mean you've found all of them?"translation="Això vol dir que les has trobades totes?"/>
</cutscene>
<cutsceneid="talkyellow_1"explanation="">
<dialoguespeaker="yellow"english="I'm making some fascinating discoveries, captain!"translation="Estic fent uns descobriments fascinants, cap!"/>
</cutscene>
<cutsceneid="talkyellow_2"explanation="">
<dialoguespeaker="yellow"english="This isn't like any other dimension we've been to, Captain."translation="Aquesta dimensió no és com les altres on hem estat, cap."/>
<dialoguespeaker="yellow"english="There's something strange about this place..."translation="Aquest lloc és estrany, per algun motiu..."/>
</cutscene>
<cutsceneid="talkyellow_3"explanation="">
<dialoguespeaker="yellow"english="Captain, have you noticed that this dimension seems to wrap around?"translation="Cap, t’has adonat que aquesta dimensió sembla que sigui cíclica? Quan en surts per un costat, apareixes per l’altre."/>
<dialoguespeaker="player"english="Yeah, it's strange..."translation="Sí, és estrany..."/>
<dialoguespeaker="yellow"english="It looks like this dimension is having the same stability problems as our own!"translation="Sembla com si aquesta dimensió tingués els mateixos problemes d’estabilitat que nosaltres!"/>
<dialoguespeaker="yellow"english="I hope we're not the ones causing it..."translation="Espero que no en siguem|els causants..."/>
<dialoguespeaker="player"english="What? Do you think we might be?"translation="Què? Creus que podem ser-ho?"/>
<dialoguespeaker="yellow"english="No no... that's very unlikely, really..."translation="No, no... És molt improbable, de fet..."/>
</cutscene>
<cutsceneid="talkyellow_4"explanation="">
<dialoguespeaker="yellow"english="My guess is that whoever used to live here was experimenting with ways to stop the dimension from collapsing."translation="Suposo que qui fos que vivia aquí experimentava amb maneres de fer que la dimensió no s’esfondrés."/>
<dialoguespeaker="yellow"english="It would explain why they've wrapped the edges..."translation="Això explicaria per què les vores esdevenen cícliques..."/>
<dialoguespeaker="yellow"english="Hey, maybe that's what's causing the interference?"translation="Escolta, potser és això, el que causa les interferències?"/>
</cutscene>
<cutsceneid="talkyellow_5"explanation="">
<dialoguespeaker="yellow"english="I wonder where the people who used to live here have gone?"translation="On deu haver anat, la gent que vivia aquí?"/>
</cutscene>
<cutsceneid="talkyellow_6"explanation="">
<dialoguespeaker="yellow"english="I think it's no coincidence that the teleporter was drawn to that dimension..."translation="Em penso que no és cap coincidència que el teletransportador anés a parar a aquella dimensió..."/>
<dialoguespeaker="yellow"english="There's something there. I think it might be causing the interference that's stopping us from leaving..."translation="Allà hi ha alguna cosa. Potser és el que causa les interferències que no ens permeten anar-nos-en..."/>
</cutscene>
<cutsceneid="talkyellow_7"explanation="">
<dialoguespeaker="yellow"english="I'm glad Verdigris is alright."translation="M’alegro que en Verdet estigui bé."/>
<dialoguespeaker="yellow"english="It'll be a lot easier to find some way out of here now that we can get the ship working again!"translation="Ara que podem fer que la nau torni a funcionar, serà molt més fàcil trobar una manera de sortir d’aquí!"/>
</cutscene>
<cutsceneid="talkyellow_8"explanation="">
<dialoguespeaker="yellow"english="Ah, you've found Doctor Victoria? Excellent!"translation="Ah, has trobat la doctora Victòria? Excel·lent!"/>
<dialoguespeaker="yellow"english="I have lots of questions for her!"translation="Li he de fer un munt de qüestions!"/>
</cutscene>
<cutsceneid="talkyellow_9"explanation="">
<dialoguespeaker="yellow"english="Vermilion says that he was trapped in some sort of tunnel?"translation="En Vermelló diu que estava atrapat en una mena de túnel?"/>
<dialoguespeaker="player"english="Yeah, it just seemed to keep going and going..."translation="Sí, sembla que es repetia una vegada rere l’altra..."/>
<dialoguespeaker="yellow"english="Interesting... I wonder why it was built?"translation="Interessant... Per què el devien construir?"/>
</cutscene>
<cutsceneid="talkyellow_10"explanation="">
<dialoguespeaker="yellow"english="It's good to be back!"translation="M’alegro de tornar a ser aquí!"/>
<dialoguespeaker="yellow"english="I've got so much work to catch up on..."translation="Tinc molta feina pendent..."/>
</cutscene>
<cutsceneid="talkyellow_11"explanation="">
<dialoguespeaker="yellow"english="I know it's probably a little dangerous to stay here now that this dimension is collapsing..."translation="Suposo que és una mica perillós estar-nos aquí ara que la dimensió s’està esfondrant..."/>
<dialoguespeaker="yellow"english="...but it's so rare to find somewhere this interesting!"translation="Però és molt poc habitual trobar|un lloc tan interessant!"/>
<dialoguespeaker="yellow"english="Maybe we'll find the answers to our own problems here?"translation="Potser hi trobarem les respostes als nostres propis problemes?"/>
</cutscene>
<cutsceneid="talkyellow_trinket1"explanation="">
<dialoguespeaker="yellow"english="Captain! I've been meaning to give this to you..."translation="Cap! Feia temps que et volia donar això..."/>
<dialoguespeaker="player"english="Professor! Where did you find this?"translation="Professor! On ho has trobat?"/>
<dialoguespeaker="yellow"english="Oh, it was just lying around that space station."translation="Ah, era en aquella estació espacial."/>
<dialoguespeaker="yellow"english="It's a pity Doctor Victoria isn't here, she loves studying that sort of thing..."translation="És una llàstima que no hi hagi la doctora Victòria. Li encanta estudiar aquesta mena de coses..."/>
<dialoguespeaker="player"english="Any idea what it does?"translation="Tens cap idea de per a què serveixen?"/>
<dialoguespeaker="yellow"english="Nope! But it is giving off a strange energy reading..."translation="No! Però tenen unes lectures d’energia estranyes..."/>
</cutscene>
<cutsceneid="talkyellow_trinket2"explanation="">
<dialoguespeaker="yellow"english="...so I used the ship's scanner to find more of them!"translation="...així que he fet servir l’escàner de la nau per a trobar-ne més!"/>
<dialoguespeaker="yellow"english="...Please don't let them distract you from finding Victoria, though!"translation="Però... que no et distreguin de trobar la Victòria, d’acord?"/>
<dialoguespeaker="yellow"english="I hope she's ok..."translation="Espero que estigui bé..."/>
</cutscene>
<cutsceneid="talkyellow_trinket3"explanation="">
<dialoguespeaker="yellow"english="Can't seem to detect any more of them nearby, though."translation="Sembla que no en detecto|cap més a prop."/>
<dialoguespeaker="yellow"english="Maybe you've found them all?"translation="Potser ja les has trobades totes?"/>
<dialoguespeaker="cyan"english="Aha! This must be what's causing the interference!"translation="Ahà! Això deu ser el que causa les interferències!"/>
<dialoguespeaker="cyan"english="I wonder if I can turn it off?"translation="Potser el podria apagar?"/>
<dialoguespeaker="gray"english="WARNING: Disabling the Dimensional Stability Generator may lead to instability! Are you sure you want to do this?"translation="ADVERTIMENT: Si desactiveu el generador d’estabilitat dimensional, podeu generar inestabilitat!|Segur que voleu fer això?"/>
<dialoguespeaker="blue"english="I knew you'd be ok!"translation="Sabia que te’n sortiries!"/>
<dialoguespeaker="purple"english="We were very worried when you didn't come back..."translation="Patíem molt per si no tornaves..."/>
<dialoguespeaker="green"english="...but when you turned off the source of the interference..."translation="...però quan has desactivat l’origen de les interferències..."/>
<dialoguespeaker="yellow"english="...we were able to find you with the ship's scanners..."translation="...hem pogut localitzar-te amb els escàners de la nau..."/>
<dialoguespeaker="red"english="...and teleport you back on board!"translation="...i teletransportar-te de nou a bord!"/>
<dialoguespeaker="player"english="That was lucky!"translation="Quina sort!"/>
<dialoguespeaker="yellow"english="...it looks like this dimension is starting to destabilise, just like our own..."translation="...sembla que aquesta dimensió s’està començant a desestabilitzar, igual que la nostra..."/>
<dialoguespeaker="red"english="...we can stay and explore for a little longer, but..."translation="Ens hi podem estar i explorar una mica més, però..."/>
<dialoguespeaker="yellow"english="...eventually, it'll collapse completely."translation="...tard o d’hora, s’esfondrarà per complet."/>
<dialoguespeaker="green"english="There's no telling exactly how long we have here. But the ship's fixed, so..."translation="No sabem exactament quant de temps tenim. Però la nau està reparada, així que..."/>
<dialoguespeaker="blue"english="...as soon as we're ready, we can go home!"translation="...tan bon punt estiguem llestos,|podrem tornar a casa!"/>
<dialoguespeaker="purple"english="What now, Captain?"translation="Què hi ha, cap?"/>
<dialoguespeaker="player"english="Let's find a way to save this dimension!"translation="Trobem una manera de salvar aquesta dimensió!"/>
<dialoguespeaker="player"english="And a way to save our home dimension too!"translation="I una manera de salvar la nostra, alhora!"/>
<dialoguespeaker="player"english="The answer is out there, somewhere!"translation="Segur que la resposta és allà fora, en algun lloc!"/>
<dialoguespeaker="blue"english="I'll run some tests and see if I can work out what they're for..."translation="Faré alguns experiments i miraré de descobrir per a què serveixen..."/>
<dialoguespeaker="player"english="That... that didn't sound good..."translation="Això... no ha sonat gaire bé..."/>
<dialoguespeaker="purple"english="This is where we were storing those shiny things? What happened?"translation="Aquí és on desàvem les coses brillants aquelles, oi? Què ha passat?"/>
<dialoguespeaker="player"english="We were just playing with them, and..."translation="Hi estàvem jugant, i..."/>
<dialoguespeaker="player"english="...they suddenly exploded!"translation="...han explotat de sobte!"/>
<dialoguespeaker="blue"english="But look what they made! Is that a teleporter?"translation="Però mireu què han fet! És un teletransportador?"/>
<dialoguespeaker="yellow"english="I think so, but..."translation="Això sembla, però..."/>
<dialoguespeaker="yellow"english="I've never seen a teleporter like that before..."translation="No he vist mai un teletransportador així..."/>
<dialoguespeaker="red"english="We should investigate!"translation="Ho hauríem d’investigar!"/>
<dialoguespeaker="purple"english="What do you think, Captain?"translation="Què en penses, cap?"/>
<dialoguespeaker="purple"english="Should we find out where it leads?"translation="Hauríem d’investigar on porta?"/>
<dialoguespeaker="purple"english="Or, you know... we could have just warped back to the ship..."translation="O bé... també hauríem pogut teletransportar-nos a la nau..."/>
<dialoguespeaker="green"english="Wow! What is this?"translation="Ostres! Què és això?"/>
<dialoguespeaker="yellow"english="It looks like another laboratory!"translation="Sembla un altre laboratori!"/>
<dialoguespeaker="red"english="Let's have a look around!"translation="Donem-hi un cop d’ull!"/>
</cutscene>
<cutsceneid="talkpurple_9"explanation="">
<dialoguespeaker="purple"english="Look at all this research! This is going to be a big help back home!"translation="Mira quanta recerca! Això ens serà de gran ajuda quan tornem a casa!"/>
</cutscene>
<cutsceneid="talkgreen_11"explanation="">
<dialoguespeaker="green"english="I wonder why they abandoned this dimension? They were so close to working out how to fix it..."translation="Per què devien abandonar aquesta dimensió? Eren molt a prop de descobrir com reparar-la..."/>
<dialoguespeaker="green"english="Maybe we can fix it for them? Maybe they'll come back?"translation="Potser la podem reparar en nom seu? Potser tornaran?"/>
</cutscene>
<cutsceneid="talkblue_9"explanation="">
<dialoguespeaker="blue"english="This lab is amazing! The scientists who worked here know a lot more about warp technology than we do!"translation="Aquest laboratori és increïble! Els científics que hi treballaven sabien moltes més coses de la tecnologia del teletransport que no pas nosaltres!"/>
</cutscene>
<cutsceneid="talkyellow_12"explanation="">
<dialoguespeaker="yellow"english="Captain! Have you seen this?"translation="Cap! Has vist això?"/>
<dialoguespeaker="yellow"english="With their research and ours, we should be able to stabilise our own dimension!"translation="Si ajuntem les seves investigacions i les nostres, potser podríem estabilitzar la nostra dimensió!"/>
<dialoguespeaker="red"english="Look what I found!"translation="Mira què he trobat!"/>
<dialoguespeaker="red"english="It's pretty hard, I can only last for about 10 seconds..."translation="És força difícil, només hi he durat uns deu segons..."/>
</cutscene>
<cutsceneid="terminal_jukebox"explanation="">
<dialoguespeaker="gray"english="-=JUKEBOX =-
Songs will continue to play until you leave the ship.
Collect trinkets to unlock new songs!" translation="-= TOCADISCOS =-
Les cançons continuaran sonant fins que surtis de la nau.
Recull lluentons per a desblocar cançons noves!" centertext="1" padtowidth="264"/>
</cutscene>
<cutsceneid="terminal_jukeunlock1"explanation="">
<dialoguespeaker="gray"english="NEXTUNLOCK:
5 Trinkets
Pushing Onwards" translation="SEGÜENT PISTA A DESBLOCAR:
5 lluentons
Prement sempre endavant" tt="1" pad="1"/>
</cutscene>
<cutsceneid="terminal_jukeunlock2"explanation="">
<dialoguespeaker="gray"english="NEXTUNLOCK:
8 Trinkets
Positive Force" translation="SEGÜENT PISTA A DESBLOCAR:
8 lluentons
Positivitat en la força" tt="1" pad="1"/>
</cutscene>
<cutsceneid="terminal_jukeunlock3"explanation="">
<dialoguespeaker="gray"english="NEXTUNLOCK:
10 Trinkets
Presenting VVVVVV" translation="SEGÜENT PISTA A DESBLOCAR:
10 lluentons
Presentació de VVVVVV" tt="1" pad="1"/>
</cutscene>
<cutsceneid="terminal_jukeunlock4"explanation="">
<dialoguespeaker="gray"english="NEXTUNLOCK:
12 Trinkets
Potential for Anything" translation="SEGÜENT PISTA A DESBLOCAR:
Pressure Cooker" translation="SEGÜENT PISTA A DESBLOCAR:
14 lluentons
Pressió dins l’olla" tt="1" pad="1"/>
</cutscene>
<cutsceneid="terminal_jukeunlock5"explanation="">
<dialoguespeaker="gray"english="NEXTUNLOCK:
16 Trinkets
Predestined Fate" translation="SEGÜENT PISTA A DESBLOCAR:
16 lluentons
Predestinació atzarosa" tt="1" pad="1"/>
</cutscene>
<cutsceneid="terminal_jukeunlock6"explanation="">
<dialoguespeaker="gray"english="NEXTUNLOCK:
18 Trinkets
Popular Potpourri" translation="SEGÜENT PISTA A DESBLOCAR:
18 lluentons
Popurri popular" tt="1" pad="1"/>
</cutscene>
<cutsceneid="terminal_jukeunlock7"explanation="">
<dialoguespeaker="gray"english="NEXTUNLOCK:
20 Trinkets
Pipe Dream" translation="SEGÜENT PISTA A DESBLOCAR:
20 lluentons
Pretensió impossible" tt="1" pad="1"/>
</cutscene>
<cutsceneid="terminal_station_1"explanation="">
<dialoguespeaker="gray"english="-= PERSONAL LOG =-"translation="-= DIARI PERSONAL =-"padtowidth="280"/>
<dialoguespeaker="gray"english="Almost everyone has been evacuated from the space station now. The rest of us are leaving in a couple of days, once our research has been completed."translation="Ja han evacuat gairebé tothom de l’estació espacial. La resta|ens n’anirem d’aquí a un parell de dies, quan hàgim acabat la recerca."pad="1"/>
</cutscene>
<cutsceneid="terminal_station_2"explanation="">
<dialoguespeaker="gray"english="-= Research Notes =-"translation="-= NOTES DE RECERCA =-"padtowidth="264"/>
<dialoguespeaker="gray"english="...everything collapses, eventually. It's the way of the universe."translation="...tot s’acaba esfondrant, tard o d’hora. L’univers és així."centertext="1"pad="1"/>
</cutscene>
<cutsceneid="terminal_station_3"explanation="">
<dialoguespeaker="gray"english="I wonder if the generator we set up in the polar dimension is what's affecting our teleporters?"translation="Potser el que afecta els nostres|teletransportadors és el generador|que vam instal·lar a la dimensió polar?"/>
<dialoguespeaker="gray"english="No, it's probably just a glitch."translation="No, segurament només és un error."/>
</cutscene>
<cutsceneid="terminal_station_4"explanation="a trinket that's difficult to get">
<dialoguespeaker="gray"english="-= PERSONAL LOG =-"translation="-= DIARI PERSONAL =-"padtowidth="280"/>
<dialoguespeaker="gray"english="Hah! Nobody will ever get this one."translation="Ha! Aquest no l’agafarà mai ningú."pad="1"/>
</cutscene>
<cutsceneid="terminal_warp_1"explanation="">
<dialoguespeaker="gray"english="...The other day I was chased down a hallway by a giant cube with the word AVOID on it."translation="L’altre dia un cub gegant amb la paraula EVITA em va perseguir per una sala."/>
<dialoguespeaker="gray"english="These security measures go too far!"translation="Aquestes mesures de seguretat són massa estrictes!"/>
</cutscene>
<cutsceneid="terminal_warp_2"explanation="">
<dialoguespeaker="gray"english="The only way into my private lab anymore is by teleporter."translation="L’única manera d’entrar al meu laboratori privat és amb un teletransportador."/>
<dialoguespeaker="gray"english="I've made sure that it's difficult for unauthorised personnel to gain access."translation="M’he assegurat que sigui difícil que personal no autoritzat hi obtingui accés."/>
</cutscene>
<cutsceneid="terminal_outside_1"explanation="">
<dialoguespeaker="gray"english="-= Research Notes =-"translation="-= NOTES DE RECERCA =-"padtowidth="264"/>
<dialoguespeaker="gray"english="... our first breakthrough was the creation of the inversion plane, which creates a mirrored dimension beyond a given event horizon ..."translation="...el nostre primer avenç va ser la creació del pla d’inversió, que crea una dimensió mirall més enllà d’un horitzó d’esdeveniments|concret..."pad="1"/>
</cutscene>
<cutsceneid="terminal_outside_2"explanation="">
<dialoguespeaker="gray"english="-= Research Notes =-"translation="-= NOTES DE RECERCA =-"padtowidth="264"/>
<dialoguespeaker="gray"english="...with just a small modification to the usual parameters, we were able to stabilise an infinite tunnel!"translation="...sols amb una petita modificació dels paràmetres normals, vam poder estabilitzar un túnel infinit!"/>
</cutscene>
<cutsceneid="terminal_outside_3"explanation="">
<dialoguespeaker="gray"english="-= Research Notes =-"translation="-= NOTES DE RECERCA =-"padtowidth="264"/>
<dialoguespeaker="gray"english="... the final step in creating the dimensional stabiliser was to create a feedback loop ..."translation="...l’últim pas a l’hora de crear l’estabilitzador dimensional va ser crear un bucle de retroalimentació..."pad="1"/>
</cutscene>
<cutsceneid="terminal_outside_4"explanation="">
<dialoguespeaker="gray"english="-= Research Notes =-"translation="-= NOTES DE RECERCA =-"padtowidth="264"/>
<dialoguespeaker="gray"english="...despite our best efforts, the dimensional stabiliser won't hold out forever. Its collapse is inevitable..."translation="...malgrat els nostres esforços, l’estabilitzador dimensional no funcionarà eternament. En algun moment, s’esfondrarà..."pad="1"/>
<dialoguespeaker="cyan"english="Huh? These coordinates aren't even in this dimension!"translation="Eh? Aquestes coordenades no són ni tan sols en aquesta dimensió!"/>
</cutscene>
<cutsceneid="terminal_outside_5"explanation="">
<dialoguespeaker="gray"english="-= Personal Log =-"translation="-= DIARI PERSONAL =-"padtowidth="232"/>
<dialoguespeaker="gray"english="... I've had to seal off access to most of our research. Who knows what could happen if it fell into the wrong hands? ..."translation="...he hagut de segellar l’accés a la major part de la recerca. Qui sap què podria passar si caigués en les mans equivocades..."centertext="1"pad="1"/>
</cutscene>
<cutsceneid="terminal_outside_6"explanation="">
<dialoguespeaker="gray"english="-= Research Notes =-"translation="-= NOTES DE RECERCA =-"padtowidth="264"/>
<dialoguespeaker="gray"english="... access to the control center is still possible through the main atmospheric filters ..."translation="...l’accés al centre de control encara és possible a través dels filtres atmosfèrics principals..."/>
</cutscene>
<cutsceneid="terminal_lab_1"explanation="">
<dialoguespeaker="gray"english="... it turns out the key to stabilising this dimension was to create a balancing force outside of it!"translation="...resulta que la clau per a estabilitzar aquesta dimensió era crear una força equilibradora fora de la dimensió!"/>
<dialoguespeaker="gray"english="Though it looks like that's just a temporary solution, at best."translation="Tot i que això sembla només una solució temporal, com a molt."/>
<dialoguespeaker="gray"english="I've been working on something more permanent, but it seems it's going to be too late..."translation="He estat treballant en una cosa més permanent, però sembla que ja serà massa tard..."/>
</cutscene>
<cutsceneid="terminal_lab_2"explanation="">
<dialoguespeaker="gray"english="?SYNTAX ERROR"translation="?ERROR DE SINTAXI"/>
</cutscene>
<cutsceneid="terminal_letsgo"explanation="">
<dialoguespeaker="player"english="Now that the ship is fixed, we can leave anytime we want!"translation="Ara que la nau ja està reparada, ens en podem anar quan vulguem!"/>
<dialoguespeaker="player"english="We've all agreed to keep exploring this dimension, though."translation="Però tots hem estat d’acord a continuar explorant aquesta dimensió."/>
<dialoguespeaker="player"english="Who knows what we'll find?"translation="Qui sap què hi trobarem!"/>
The Super-Gravitron is intended for entertainment purposes only." translation="-= ADVERTIMENT =-
El supergravitró només té finalitats d’entreteniment." centertext="1" pad="1"/>
<dialoguespeaker="gray"english="Anyone found using the Super Gravitron for educational purposes may be asked to stand in the naughty corner."translation="Si algú el fa servir per a finalitats educatives, és|possible que l’enviem al racó de pensar."/>
Controls de navegació de la nau" centertext="1" pad="1"/>
<dialoguespeaker="gray"english="Error! Error! Cannot isolate dimensional coordinates! Interference detected!"translation="Error! Error! No és possible aïllar les coordenades dimensionals! S’han detectat interferències!"/>
</cutscene>
<cutsceneid="alreadyvisited"explanation="">
<dialoguespeaker="cyan"english="...oh, I've already found this."translation="Oh, això ja ho havia trobat..."/>
</cutscene>
<cutsceneid="disableaccessibility"explanation="">
<dialoguespeaker="gray"english="Please disable invincibility and/or slowdown before entering the Super Gravitron."translation="Desactiva la invencibilitat i/o l’alentiment abans d’entrar al supergravitró."/>
<!-- You can translate these in-game to get better context! See README.txt -->
<roomnames>
<roomnamex="0"y="18"english="Single-slit Experiment"translation="Experiment de l’única escletxa"explanation="(Many of the rooms in the Lab stage have science themed names.)"/>
<roomnamex="0"y="19"english="Don't Flip Out"translation="Cap per avall i cap per amunt"explanation="Flip as in gravity flip, but also the expression in english, as in, keep your cool"/>
<roomnamex="1"y="0"english="I'm Sorry"translation="Em sap greu..."explanation="The room below this one is Please Forgive Me. There is also a secret path to the right which leads to the rooms 'Anomaly' and 'Purest Unobtainium'."/>
<roomnamex="1"y="1"english="Please Forgive Me!"translation="Perdona’m, si us plau!"explanation="The room above this one is I'm Sorry"/>
<roomnamex="1"y="17"english="Rascasse"translation="Escórpora"explanation="This is a type of fish with lots of thorny spikes on its back"/>
<roomnamex="1"y="18"english="Keep Going"translation="Continua endavant"explanation="Literally just 'Keep Going', through the room"/>
<roomnamex="1"y="19"english="Shuffled Hallway"translation="Corredor remogut"explanation="Just describes how the room looks. 'Shuffled' as in offset."/>
<roomnamex="2"y="0"english="Kids His Age Bounce"translation="Els nens d’aquesta edat són de goma"explanation="An old saying for when e.g. a small child falls out of a tree. A trampoline joke in this case."/>
<roomnamex="2"y="1"english="Playing Foosball"translation="Hora de jugar al futbolí"explanation="This room resembles a Foosball table - https://en.wikipedia.org/wiki/Table_football"/>
<roomnamex="2"y="4"english="Philadelphia Experiment"translation="L’experiment Filadèlfia"explanation="There is a teleporter in this room. The Philadelphia Experiment is the name of 80's film about teleportation."/>
<roomnamex="2"y="16"english="Get Ready To Bounce"translation="Prepara’t per a rebotar"explanation="The first room in the Lab zone. In the next room, you immediately come into contact with a gravity line, and flip back up."/>
<roomnamex="2"y="17"english="It's Perfectly Safe"translation="No pateixis, és totalment segur"explanation="The second room in the lab zone. Don't worry, it's perfectly safe."/>
<roomnamex="2"y="18"english="Young Man, It's Worth the Challenge"translation="Xiquets, el desafiament paga la pena"explanation="Not a reference to anything in particular - Bennett explains that this is just something that his high school chemistry teacher used to say to students. He thinks the teacher was probably misquoting George Bernard Shaw, who said 'Life is not meant to be easy, my child - but take courage: it can be delightful.'."/>
<roomnamex="2"y="19"english="Double-slit Experiment"translation="Experiment de la doble escletxa"explanation="(Many of the rooms in the Lab stage have science themed names.)"/>
<roomnamex="3"y="0"english="Merge"translation="Estrenyiment"explanation="A wide section connecting to a narrower section - merge as in traffic"/>
<roomnamex="3"y="1"english="A Difficult Chord"translation="Uns acords difícils"explanation="This room resembles a guitar chord"/>
<roomnamex="3"y="4"english="Why So Blue?"translation="Una sala blava i trista"explanation="The crewmate Victoria is found here. Victoria is always sad! She's feeling blue."/>
<roomnamex="3"y="16"english="Brought to you by the letter G"translation="Compte amb la gravetat"explanation="Reference to Seseme Street - also, this room resembles the letter G"/>
<roomnamex="3"y="17"english="Thorny Exchange"translation="Passades entre punxes"explanation="Two people having a polite argument could be described as having a Thorny Exchange of words."/>
<roomnamex="3"y="18"english="Square Root"translation="Arrel quadrada"explanation="This room resembles a Square Root symbol."/>
<roomnamex="3"y="19"english="They Call Him Flipper"translation="Gira que giraràs"explanation="This is a line from an American TV show intro about a Dolphin - https://www.youtube.com/watch?v=azEOeTX1LqM"/>
<roomnamex="4"y="0"english="Vibrating String Problem"translation="Problema de la corda vibrant"explanation="Another science themed room name, this one has two gravity lines that you bounce between."/>
<roomnamex="4"y="1"english="The Living Dead End"translation="L’atzucac més extrem"explanation="'The living end' is an idiom meaning 'the most extreme form of something', here it's an extreme dead end, i.e. a cul-de-sac"/>
<roomnamex="4"y="2"english="AAAAAA"translation="AAAAAA"explanation="The player falls through this room without having time to stop - AAAAAA suggests a scream in English. Also, it's six As, like the title."/>
<roomnamex="4"y="3"english="Diode"translation="Díode"explanation="This room can only be passed through in one direction. It resembles the electrical component."/>
<roomnamex="4"y="4"english="I Smell Ozone"translation="Sento olor d’ozó"explanation="When you use a photocopier, it produces a distinctive smell (from the Ozone produced). This room has a background pattern that suggests a teleportation has recently happened here."/>
<roomnamex="4"y="16"english="Free Your Mind"translation="Allibera la ment"explanation="Reference to the film The Matrix, where Morpheus jumps from the top of a Skyscaper - https://www.youtube.com/watch?v=ef_agVIvh0A"/>
<roomnamex="4"y="17"english="I Changed My Mind, Thelma..."translation="He canviat d’idea, Thelma..."explanation="The room below Free Your Mind. Reference to the film Thelma and Louise, which ends with Thelma and Louise driving off a cliff, sorry, spoilers"/>
<roomnamex="4"y="18"english="Hitting the Apex"translation="Derrapada ajustada"explanation="'Hitting the Apex' is the term used by race drivers for the optimal path around a corner"/>
<roomnamex="4"y="19"english="Three's a Crowd"translation="Dos són companyia, tres són multitud"explanation="From the expression Two's Company, Three's a Crowd. This room has two challenges - the first has two gaps to cross, the second has three."/>
<roomnamex="5"y="0"english="Spike Strip Deployed"translation="Tira de punxes desplegada"explanation="This room has some spikes on a gravity line. The name is a reference to the device that police might use to blow out the tyres of a speeding car."/>
<roomnamex="5"y="1"english="Anomaly"translation="Anomalia"explanation="As in, a strange result in science. This room has lots of different colours, unlike other rooms in this stage."/>
<roomnamex="5"y="16"english="In a Single Bound"translation="D’un sol salt"explanation="Superman is described as being able to leap tall buildings in a single bound."/>
<roomnamex="5"y="17"english="Indirect Jump Vector"translation="Vector indirecte de salt"explanation="If you miss the gap in 'In a Single Bound' above, you will end up back in this room - hence, your trajectory was off!"/>
<roomnamex="6"y="0"english="Topsy Turvyism"translation="Sacsejat, no remenat"explanation="Topsy Turvy is Australian slang for upside down"/>
<roomnamex="6"y="1"english="Purest Unobtainium"translation="L’inobteni més pur"explanation="Unobtainium is a jokey made up term from science fiction for an impossible substance - https://en.wikipedia.org/wiki/Unobtainium"/>
<roomnamex="6"y="16"english="Barani, Barani"translation="Un barani rere l’altre"explanation="A Barani is a technical term for doing a flip on a trampoline"/>
<roomnamex="6"y="17"english="Safety Dance"translation="Podré tornar enrere"explanation="Named after the 80s song by Men Without Hats."/>
<roomnamex="7"y="0"english="Standing Wave"translation="Ona estacionària"explanation="Many of the rooms in the Lab stage have science themed names. This one is at the beginning of a section with gravity lines above and below you, before the section begins."/>
<roomnamex="7"y="15"english="Entanglement Generator"translation="Generador d’entrellaçament"explanation="This room contains a teleporter. Entanglement is an idea from quantum mechanics."/>
<roomnamex="7"y="16"english="Heady Heights"translation="Altures embriagadores"explanation="Just below the highest point in the level."/>
<roomnamex="7"y="17"english="Exhausted?"translation="Vols sortir del pou?"explanation="This room has an exit that sort of suggests an Exhaust Pipe in a car."/>
<roomnamex="7"y="18"english="The Tantalizing Trinket"translation="El lluentó temptador"explanation="You see this Trinket just out of reach as you fall through the room."/>
<roomnamex="7"y="19"english="The Bernoulli Principle"translation="El principi de Bernoulli"explanation="Many of the rooms in the Lab stage have science themed names - this one is just named after a formula relating to flight."/>
<roomnamex="8"y="9"english="Teleporter Divot"translation="La marca del teletransportador"explanation="There is a pattern in the background of this room that indicates that a teleporter has sent someone to this room"/>
<roomnamex="9"y="9"english="The Tower"translation="La Torre"explanation="This room is a single vertically scrolling stage, about 20 rooms high. The name is from the Tarot card."/>
<roomnamex="10"y="4"english="Seeing Red"translation="Una visió roja"explanation="This is the room that you find the red crewmate in. (Seeing Red is an expression in English about being filled with rage, but that doesn't really apply here)"/>
<roomnamex="10"y="5"english="Energize"translation="Endavant!"explanation="There is a teleporter in this Room. Energize is what they say on Star Trek when they use the teleporters."/>
<roomnamex="10"y="6"english="Down Under"translation="Avall i a sota"explanation="Australia is sometimes called The Land Down Under because of its position on a globe. You complete this room by going down and under some moving platforms"/>
<roomnamex="10"y="7"english="A Deception"translation="Enganyifa"explanation="This room appears trivial at first, but is connected to a difficult trinket challenge"/>
<roomnamex="11"y="4"english="Building Apport"translation="Acord per al teletransport"explanation="An 'Apport' is a kind of paranormal teleportation. Building Apport is a pun on the concept of 'building rapport' (except that to 'apport' is to teleport)."/>
<roomnamex="11"y="5"english="Frown Upside Down"translation="Vinga, anima’t!"explanation="To 'Turn that Frown Upside Down' is an expression in English, like 'cheer up', basically means to stop being unhappy"/>
<roomnamex="11"y="6"english="Shenanigan"translation="Tripijoc"explanation="Shenanigan as in a prank, or practical joke. This room is connected to the 'Prize for the Reckless' puzzle, and like the room 'A Deception', it appears trivial unless you know the secret"/>
<roomnamex="11"y="7"english="Prize for the Reckless"translation="Un premi per la imprudència"explanation="This room contains a trinket that can only be collected by doing something difficult"/>
<roomnamex="11"y="11"english="Conveying a New Idea"translation="Deixa que et transportin"explanation="This is the first room you encounter that has conveyor belts in it"/>
<roomnamex="11"y="12"english="One Way Room"translation="Sala unidireccional"explanation="Can only be travelled through in one direction"/>
<roomnamex="11"y="13"english="Boldly To Go"translation="Penetrant allà on no ha estat mai ningú"explanation="Star Trek reference, this rooms is near the entrance to the space station level"/>
<roomnamex="11"y="14"english="The Filter"translation="El filtre"explanation="Like a filter from an air conditioning vent"/>
<roomnamex="12"y="3"english="Security Sweep"translation="Escaneig de seguretat"explanation="Contains a single, fast moving enemy that moves up and down."/>
<roomnamex="12"y="4"english="Gantry and Dolly"translation="Pòrtic i plataforma mòbil"explanation="Gantry and Dolly are the names for types of cranes that move crates around. This room has two different types of platforms."/>
<roomnamex="12"y="5"english="The Yes Men"translation="Els homes del sí"explanation="Contains a number of enemies with briefcases and the word -YES- for a head. An expression for people who work at large companies and agree a lot with their bosses"/>
<roomnamex="12"y="6"english="Stop and Reflect"translation="Atura’t i reflexiona"explanation="Expression meaning to take a moment and think about what you're doing. In this room, a small puzzle where you need to use the underside of a moving platform to progress."/>
<roomnamex="12"y="7"english="V Stitch"translation="Punt en V"explanation="A V Stitch is a type of crochet stitch."/>
<roomnamex="12"y="11"english="Upstream Downstream"translation="Riu amunt i riu avall"explanation="As in swimming upstream or downstream, with or against a current in a river"/>
<roomnamex="12"y="12"english="The High Road is Low"translation="El camí més elevat és el més baix"explanation="This room has two paths - a high path and a low path. The 'low' path leads to a trinket, so the roomname is a sort of clue about which way to go."/>
<roomnamex="12"y="13"english="Give Me A V"translation="Vèncer s’escriu amb V"explanation="Room is in the shape of a big letter V. The roomname suggests the common american cheerleading chant - e.g. Give me an L! Give me an O! Give me a C! Give me an A! Give me an L! Give me an I! Give me an S! Give me an A! Give me a T! Give me an I! Give me an O! Give me an N! What does it spell? LOCALISATION!"/>
<roomnamex="12"y="14"english="Outer Hull"translation="Coberta exterior"explanation="The entrance to the Space Station 2 level - the outer hull of a space station."/>
<roomnamex="13"y="0"english="It's Not Easy Being Green"translation="No és fàcil ser verd"explanation="References a song by Kermit from the Muppets. This room is where you find the green crewmate."/>
<roomnamex="13"y="3"english="Linear Collider"translation="Col·lisionador lineal"explanation="An early room, name is just meant to suggest something sciency. Room contains long, wave like enemies."/>
<roomnamex="13"y="4"english="Comms Relay"translation="Repetidor de comunicacions"explanation="This room contains some communication equipment, like a radio."/>
<roomnamex="13"y="5"english="Welcome Aboard"translation="Et donem la benvinguda a bord"explanation="The first room in the game"/>
<roomnamex="13"y="6"english="Trench Warfare"translation="Guerra de trinxeres"explanation="Room contains a couple of pits with soldier-like enemies in them. Loosely references the 1983 videogame Hunchback."/>
<roomnamex="13"y="7"english="B-B-B-Busted"translation="Això és un abús!"explanation="Room contains a large Bus. 'Bus'ted as in 'Caught'."/>
<roomnamex="13"y="8"english="Level Complete!"translation="Nivell completat!"explanation="This room has a teleporter, which is normally found at the end of a level. However this room is midway through the stage."/>
<roomnamex="13"y="9"english="Lighter Than Air"translation="Més volàtil que l’aire"explanation="This room has clouds that rise from the bottom of the screen to the top, which the player is faster than, implying that the player is lighter than air."/>
<roomnamex="13"y="10"english="The Solution is Dilution"translation="La solució és la dilució"explanation="This room has a factory and pollution clouds in it. Apparently this phrase was once used by industrialists to advocate for not worrying too much about pollution."/>
<roomnamex="13"y="11"english="The Cuckoo"translation="El cucut"explanation="This room contains a speaker that emits the word 'LIES' over and over. A cuckoo's call decieves other birds!"/>
<roomnamex="13"y="12"english="Backsliders"translation="Els tres bessons"explanation="A conveyor belt in this room pushes against you as you try to move, so you slide backwards."/>
<roomnamex="13"y="13"english="Select Track"translation="Tria un camí"explanation="There are two paths you can pick between here"/>
<roomnamex="14"y="0"english="Green Dudes Can't Flip"translation="Els verds no la saben invertir"explanation="You have a green crewmate with you in this room! A reference to the 90's film 'White Guy's Can't Jump'."/>
<roomnamex="14"y="1"english="This is how it is"translation="Així són les coses"explanation="literally as in, this is how the mechanic of this stage works - also an expression as in 'this is the way things are'"/>
<roomnamex="14"y="2"english="That's Why I Have To Kill You"translation="I per això t’haig de matar"explanation="The follows the room named 'I love you'. 'I love you, that's why I have to kill you' is kind of a slasher horror trope."/>
<roomnamex="14"y="3"english="Atmospheric Filtering Unit"translation="Unitat de filtratge atmosfèric"explanation="An early room, looks a bit like an air filter"/>
<roomnamex="14"y="4"english="It's a Secret to Nobody"translation="No és cap secret per a ningú"explanation="A reference to the infamous Zelda quote 'It's a secret to everybody'. This room contains the first trinket."/>
<roomnamex="14"y="5"english="Conundrum"translation="Enigma"explanation="Conundrum as in puzzle, riddle, problem to be solved"/>
<roomnamex="14"y="6"english="Boo! Think Fast!"translation="Ei, actua de pressa!"explanation="Contains a challenge that you need to react very quickly to. You might say 'Boo, think fast' if you threw something at someone, expecting them to catch it."/>
<roomnamex="14"y="7"english="The Sensible Room"translation="La sala sensata"explanation="Early corridor room containing no challenges. Sensible as in the opposite of Foolish - you might call someone sensible in english if they are excessively cautious."/>
<roomnamex="14"y="8"english="The Hanged Man, Reversed"translation="El Penjat, del revés"explanation="Named after the Tarot Card, reversed as in Upside Down. The room contains a stationary enemy which resembles a Wheel of Fortune. The name is supposed to suggest a kind of out-of-place quality."/>
<roomnamex="14"y="9"english="Green Grotto"translation="Cova verda"explanation="A peaceful green room."/>
<roomnamex="14"y="10"english="Manic Mine"translation="Mina maníaca"explanation="A reference to the 8-bit game Manic Miner."/>
<roomnamex="14"y="11"english="Clarion Call"translation="Crida a l’acció"explanation="A 'Clarion Call' is an idiom used when somebody makes a case for a course of action, for example in a politician's speech, or a call to battle. It sometimes has an association with dishonesty - in this room, the words 'LIES' appear over and over."/>
<roomnamex="14"y="12"english="Gordian Knot"translation="Nus gordià"explanation="As in the Gordian Knot from greek history. A complicated room that can be passed through twice."/>
<roomnamex="14"y="13"english="You Chose... Poorly"translation="Has triat... malament"explanation="This room comes right after a choice between two paths. It's a quote from an Indiana Jones film."/>
<roomnamex="15"y="0"english="Murdering Twinmaker"translation="Fabricador i assassinador de bessons"explanation="Room contains a teleporter. A 'Murdering Twinmaker' is, uh, one way teleportation might work..."/>
<roomnamex="15"y="1"english="A Bisected Spiral"translation="Una espiral biseccionada"explanation="Room is a spiral, cut down the middle"/>
<roomnamex="15"y="2"english="Take the Red Pill"translation="Agafa la píndola vermella"explanation="This is a Matrix reference that hasn't aged well, lol"/>
<roomnamex="15"y="3"english="Traffic Jam"translation="Embús de trànsit"explanation="The enemies in this room are Stop Signs"/>
<roomnamex="15"y="4"english="Leap of Faith"translation="Salt de fe"explanation="To take a leap of faith means to do something without knowing how it's going to turn out."/>
<roomnamex="15"y="5"english="Solitude"translation="Soledat"explanation="As in being alone, or in this case, lost by yourself"/>
<roomnamex="15"y="6"english="Driller"translation="Perforació"explanation="Technically references the name of a C64 game, but that doesn't matter much"/>
<roomnamex="15"y="7"english="Exhaust Chute"translation="Conducte d’evacuació de la brossa"explanation="Like a factory exhaust chute for disposing of rubbish"/>
<roomnamex="15"y="8"english="Sorrow"translation="Pena"explanation="A difficult room that you might die in a lot"/>
<roomnamex="15"y="9"english="doomS"translation="sosoxund suowqnS"explanation="The room is above 'Swoop', and is a copy of the room rotated 180 degrees! The room name 'doomS' is the word 'Swoop' rotated 180 degrees. When localising this room, don't worry too much about trying to keep the meaning of the words Dooms and Swoop, because they're not that important - instead, focus on picking words that have this 180 degree flip quality!"/>
<roomnamex="15"y="10"english="Swoop"translation="Submons punxosos"explanation="See the note for room (15,9), doomS."/>
<roomnamex="15"y="11"english="Chinese Rooms"translation="L’habitació xinesa"explanation="This refers to a famous philosophical argument about artifical intelligence - https://en.wikipedia.org/wiki/Chinese_room."/>
<roomnamex="15"y="12"english="You Just Keep Coming Back"translation="No fas res més que tornar aquí"explanation="You can pass through this room up to three times, depending on which route you take through the level."/>
<roomnamex="15"y="13"english="Hyperspace Bypass 5"translation="Circumval·lació espacial 5"explanation="A conveyor belt will take you through this room without you needing to press any buttons, hench the bypass. The phrase Hyperspace Bypass is a reference to Hitchhiker's Guide to the Galaxy."/>
<roomnamex="16"y="0"english="I Love You"translation="T’estimo"explanation="The room contains a couple of heart shaped enemies"/>
<roomnamex="16"y="1"english="As you like it"translation="Al vostre gust"explanation="This room can be approached in two different equivilent ways, whichever way you like it. 'As you like it' is the name of a Shakespeare play."/>
<roomnamex="16"y="2"english="Short Circuit"translation="Curtcircuit"explanation="Probably named after the 80s film Short Circuit. Also works because you'll hit a dead end if you keep walking forwards."/>
<roomnamex="16"y="3"english="Twisty Little Passages"translation="Petits corredors recaragolats"explanation="A maze like room. Refers to the section from the 1976 text game Colossal Cave Adventure - you are in a maze of twisty little passages, all alike"/>
<roomnamex="16"y="6"english="Quicksand"translation="Arenes movedisses"explanation="Contains lots of dissolving platforms."/>
<roomnamex="16"y="7"english="The Tomb of Mad Carew"translation="La tomba de Mad Carew"explanation="A very obscure reference to the C64 game Dizzy"/>
<roomnamex="16"y="8"english="Parabolica"translation="Parabòlica"explanation="This room contains a section of wall in the shape of a parabolic arch."/>
<roomnamex="16"y="9"english="$eeing Dollar $ign$"translation="El dolor del dòlar"explanation="This is a green room that resembles a dollar sign shape"/>
<roomnamex="16"y="10"english="What Lies Beneath?"translation="Què hi ha a sota?"explanation="The room below this contains enemies in the shape of the word 'Lies'. So there's a double meaning here - as in, a question, 'what is below this room', and that the word LIES is literally beneath this room."/>
<roomnamex="16"y="11"english="Spikes Do!"translation="Hi ha punxes!"explanation="This rooms is below the room named 'What lies Beneath?', and answers the question: Spikes do!"/>
<roomnamex="16"y="12"english="Ha Ha Ha Not Really"translation="Ha, ha, ha! La veritat és que no!"explanation="This is a difficult room that follows one called 'Plain Sailing from here on'. It's taunting the player. "/>
<roomnamex="16"y="13"english="Plain Sailing from Here On"translation="A partir d’aquí tot seran flors i violes"explanation="This room is at the end of a long section, and promises 'Plain Sailing' afterwards, as in, no further challenges. This is a lie"/>
<roomnamex="17"y="0"english="As we go up, we go down"translation="El sostre és el terra i al revés"explanation="Named after the 1995 song by Guided by Voices"/>
<roomnamex="17"y="1"english="Maze With No Entrance"translation="Un laberint sense entrada"explanation="This room is a maze which has no entrance, due to the nature of the warping mechanic."/>
<roomnamex="17"y="2"english="The Brown Gate"translation="La porta marró"explanation="I think this is an Ultima 7 reference? A literal translation is fine here"/>
<roomnamex="17"y="3"english="Edge Games"translation="El joc de les vores"explanation="Contains a trinket that you get by navigating around the edge of the screen. The use of the word EDGE is deliberate, refering to the trademark of a notoriously litigious individual who sued an indie developer around the time VVVVVV was made"/>
<roomnamex="17"y="7"english="Brass Sent Us Under The Top"translation="Napoleó tenia cent soldats"explanation="The enemies in this room look like little army guys"/>
<roomnamex="17"y="8"english="The Warning"translation="Agafa’t fort"explanation="This room has lots of checkpoints in it. It's beside the game's most difficult challenge, the Veni, Vidi, Vici section. The checkpoints don't really do anything, but they're a warning of the challenge ahead."/>
<roomnamex="17"y="9"english="Just Pick Yourself Down"translation="...aixeca’t"explanation="A two part room name. From the expression 'If you fall down, just pick yourself up'."/>
<roomnamex="17"y="10"english="If You Fall Up"translation="Si caus..."explanation="A two part room name. From the expression 'If you fall down, just pick yourself up'."/>
<roomnamex="17"y="11"english="Chipper Cipher"translation="El receptor que s’ho empassa tot"explanation="Just some nice wordplay. Chipper means 'Jolly' or 'Happy'."/>
<roomnamex="18"y="0"english="Time to get serious"translation="És l’hora de posar-nos seriosos"explanation="Literal, this is the first room in the stage which is fairly difficult"/>
<roomnamex="18"y="1"english="Wheeler's Wormhole"translation="Forat de cuc de Wheeler"explanation="Apparently a scientist named John Wheeler coined the phrase 'Wormhole'! I just found that out. Anyway, this room has a teleporter in it."/>
<roomnamex="18"y="2"english="Sweeney's Maze"translation="El laberint de Sweeney"explanation="Contains enemies that move strangly and resemble enemies from ZZT, an old game by Tim Sweeney"/>
<roomnamex="18"y="3"english="Mind The Gap"translation="Vigileu amb la distància cotxe-andana"explanation="Refers to what train announcers say on the London Underground when you leave the train - mind the gap between the train and the station platform"/>
<roomnamex="18"y="7"english="A Wrinkle in Time"translation="Un replec en el temps"explanation="The name of a 60's science fiction book. There is a teleporter in this room."/>
<roomnamex="18"y="8"english="Getting Here is Half the Fun"translation="Arribar aquí és sols la meitat del camí"explanation="The top of the Veni Vidi Vici sequence. When you get to the top, you have to go all the way back down - hence, this room is the halfway point of the challenge."/>
<roomnamex="18"y="9"english="Your Bitter Tears... Delicious"translation="Les teves llàgrimes... Delicioses!"explanation="Part of the Veni Vidi Vici sequence. Taunting the player."/>
<roomnamex="18"y="10"english="Easy Mode Unlocked"translation="Mode fàcil desblocat"explanation="Part of the Veni Vidi Vici sequence. Taunting the player - the 'easy mode' refers to the second passageway on the right side that is easier to take when going back through this room on the way down, but that passageway leads to death."/>
<roomnamex="18"y="11"english="Vici!"translation="Vici!"explanation="The rooms Veni, Vidi, Vici! appear in sequence. Famous latin phrase attributed to Julius Caesar meaning I came, I saw, I conquered. The hardest challenge in the game."/>
<roomnamex="18"y="12"english="Vidi"translation="Vidi"explanation="The rooms Veni, Vidi, Vici! appear in sequence. Famous latin phrase attributed to Julius Caesar meaning I came, I saw, I conquered. The hardest challenge in the game."/>
<roomnamex="18"y="13"english="Veni"translation="Veni"explanation="The rooms Veni, Vidi, Vici! appear in sequence. Famous latin phrase attributed to Julius Caesar meaning I came, I saw, I conquered. The hardest challenge in the game."/>
<roomnamex="18"y="14"english="Doing Things The Hard Way"translation="Les coses es fan de la manera difícil"explanation="The starting room from the Veni, Vidi, Vici! challenge."/>
<roomnamex="19"y="0"english="To The Batcave!"translation="A la Batcova!"explanation="The Batcave, as in Batman's hideout."/>
<roomnamex="19"y="1"english="Ascending and Descending"translation="Pujades i baixades"explanation="Just meant as in literally Ascending and Descending, going up and down. There is a room later in the game called Upstairs, Downstairs, which is a callback to this."/>
<roomnamex="19"y="2"english="Shockwave Rider"translation="El genet de l’ona de xoc"explanation="Named after a 70's Science Fiction novel"/>
<roomnamex="19"y="3"english="This will make you flip"translation="Acabaràs amb el cervell regirat"explanation="Flip is used here in the sense 'Flip out', as in, to lose your temper"/>
<roomnamex="41"y="51"english="1950 Silverstone Grand V"translation="Gran Vremi de Silverstone de 1950"explanation="Refers to 1950 Silverstone Grand Prix (The final stage has room names that suggest old black and white TV shows.)"/>
<roomnamex="41"y="52"english="DIY V Repair"translation="Repara la V sense ajuda externa"explanation="DIY TV repair - this room has a television you can interact with, which changes the theme from black and white to colour"/>
<roomnamex="41"y="56"english="Now Take My Lead"translation="Ara segueix-me"explanation="This entire intermission section has a tone of a strict schoolteacher leading a small child."/>
<roomnamex="42"y="52"english="Party Time!"translation="És l’hora de la festa!"explanation="The first room after the black and white section. Doesn't refer to any TV show."/>
<roomnamex="42"y="56"english="What Are You Waiting For?"translation="Ara què esperes?"explanation="This entire intermission section has a tone of a strict schoolteacher leading a small child."/>
<roomnamex="43"y="51"english="The Voon Show"translation="Mare Vostrum"explanation="Named after The Goon Show (The final stage has room names that suggest old black and white TV shows.)"/>
<roomnamex="43"y="52"english="Upstairs, Downstairs"translation="A dalt i a baix"explanation="Named after the 70s TV show - but also, refers to the earlier room Ascending and Descending, which this room is an updated version of. (Second part of the final stage has references to Colour TV shows)"/>
<roomnamex="43"y="56"english="Don't Get Ahead of Yourself!"translation="No vulguis avançar tan de sobte!"explanation="This entire intermission section has a tone of a strict schoolteacher leading a small child."/>
<roomnamex="44"y="51"english="Vertigo"translation="Vertigen"explanation="Named after the Hitchcock film (The final stage has room names that suggest old black and white TV shows.)"/>
<roomnamex="44"y="52"english="Timeslip"translation="Les tres bessones"explanation="Named after the 70s TV show - name also suggests a connection to the earlier room Backsliders, which this room is an updated version of (Second part of the final stage has references to Colour TV shows)"/>
<roomnamex="44"y="56"english="Very Good"translation="Molt bé!"explanation="This entire intermission section has a tone of a strict schoolteacher leading a small child."/>
<roomnamex="45"y="52"english="Three's Company"translation="Dos són companyia, tres són fantasia"explanation="Named after the 70s sitcom - but also, suggests a connection to the earlier room Two's Company, which this room is an updated version of (Second part of the final stage has references to Colour TV shows)"/>
<roomnamex="45"y="56"english="Must I Do Everything For You?"translation="Què vols, que t’ho faci tot jo?"explanation="This entire intermission section has a tone of a strict schoolteacher leading a small child."/>
<roomnamex="46"y="54"english="Temporary Fault..."translation="Error temporal..."explanation="Opening room of the final level."/>
<roomnamex="46"y="56"english="Now Stay Close To Me..."translation="Ara no te’n vagis gaire lluny..."explanation="This entire intermission section has a tone of a strict schoolteacher leading a small child."/>
<roomnamex="47"y="52"english="Cosmic Creepers"translation="Salem Saberhagen"explanation="Named after the cat from the 70s film Bedknobs and Broomsticks. Not sure why!"/>
<roomnamex="47"y="54"english="Do Not Adjust the V-hold"translation="No ajusteu la sincronització vertical"explanation="I don't think V-hold is a real thing, it's supposed to just suggest 'Do not adjust your TV settings'. (The final stage has room names that suggest old black and white TV shows.)"/>
<roomnamex="47"y="56"english="...But Not Too Close"translation="...però tampoc tan a prop!"explanation="In this room, if you go too quickly, your crewmate will walk into spikes."/>
<roomnamex="48"y="52"english="The Villi People"translation="Les vellositats de l’intestí"explanation="Bennett just thought this room looked 'intestinal'. Villi as in part of the intestinal system. Also refers to the 80s band The Village People."/>
<roomnamex="48"y="54"english="Regular Service Will Return Shortly"translation="El servei es recuperarà ben aviat"explanation="This is something you might hear on a TV station if they had lost reception. (The final stage has room names that suggest old black and white TV shows. A really, really good way to translate this level would be to use the names of black and white TV shows that are well known in your language, rather than trying to keep the exact meaning of the TV shows used here.)"/>
<roomnamex="48"y="56"english="Don't Be Afraid"translation="No tinguis por"explanation="This entire intermission section has a tone of a strict schoolteacher leading a small child."/>
<roomnamex="49"y="52"english="Panic Room"translation="La sala del pànic"explanation="A panic room is a safe room that you can hide in during an emergency, but I don't think that was Bennett's intention with this name. This room suddenly starts scrolling as soon as you enter, causing a panic."/>
<roomnamex="49"y="54"english="Origami Room"translation="Sala d’origami"explanation="As in folded paper - this room is mirrored around the center point."/>
<roomnamex="49"y="56"english="Do as I Say..."translation="Fes el que et dic..."explanation="This entire intermission section has a tone of a strict schoolteacher leading a small child."/>
<roomnamex="50"y="51"english="The V Stooges"translation="Els V guillats"explanation="Refers to the 3 Stooges (The final stage has room names that suggest old black and white TV shows.)"/>
<roomnamex="50"y="52"english="1954 World Cup Vinyl"translation="Vinil de la Copa del Món de 1954"explanation="Refers to the World Cup Final (The final stage has room names that suggest old black and white TV shows.)"/>
<roomnamex="50"y="56"english="...Not as I Do"translation="...no el que jo faig"explanation="This entire intermission section has a tone of a strict schoolteacher leading a small child."/>
<roomnamex="51"y="53"english="The Final Challenge"translation="L’últim repte"explanation="One of the last challenges in the game."/>
<roomnamex="51"y="56"english="Mind Your Head"translation="Vés amb compte amb el cap"explanation="This entire intermission section has a tone of a strict schoolteacher leading a small child."/>
<roomnamex="52"y="53"english="The Last Straw"translation="La gota que fa vessar el got"explanation="One more little challenge, just after the room called The Final Challenge"/>
<roomnamex="52"y="56"english="Do Try To Keep Up"translation="Mira de seguir-me"explanation="This entire intermission section has a tone of a strict schoolteacher leading a small child."/>
<roomnamex="53"y="48"english="Whee Sports"translation="Wiii, esports!"explanation="Refers to Wii Sports, the Nintendo Wii launch title. 'Whee' as in what a child might say while going down a slide (this room has a long drop in it)"/>
<roomnamex="53"y="49"english="Whizz Down The Shaft"translation="Afanya’t i baixa pel pou"explanation="Whizz is Australian slang for doing something quickly, I think"/>
<roomnamex="53"y="50"english="The Gravitron"translation="El gravitró"explanation="A special arcade section where you have to survive for 60 seconds. Bennett named this one so as to suggest a funfair ride (though not any one in particular)."/>
<roomnamex="53"y="51"english="Tunnel of Terror"translation="El túnel del terror"explanation="Another name inspired by funfairs."/>
<roomnamex="53"y="52"english="House of Mirrors"translation="La casa dels miralls"explanation="Another name inspired by funfairs."/>
<roomnamex="53"y="53"english="W"translation="V doble"explanation="This room has platforms in a W shape."/>
<roomnamex="53"y="56"english="You're Falling Behind"translation="T’estàs endarrerint"explanation="This entire intermission section has a tone of a strict schoolteacher leading a small child."/>
<roomnamex="54"y="48"english="VVVVVV"translation="VVVVVV"explanation="Final sequence of rooms that spell out V-V-V-V-V-V, 6/6"/>
<roomnamex="54"y="49"english="VVVVV"translation="VVVVV"explanation="Final sequence of rooms that spell out V-V-V-V-V-V, 5/6"/>
<roomnamex="54"y="50"english="VVVV"translation="VVVV"explanation="Final sequence of rooms that spell out V-V-V-V-V-V, 4/6"/>
<roomnamex="54"y="51"english="VVV"translation="VVV"explanation="Final sequence of rooms that spell out V-V-V-V-V-V, 3/6"/>
<roomnamex="54"y="52"english="VV"translation="VV"explanation="Final sequence of rooms that spell out V-V-V-V-V-V, 2/6"/>
<roomnamex="54"y="53"english="V"translation="V"explanation="Final sequence of rooms that spell out V-V-V-V-V-V, 1/6"/>
<roomnamex="54"y="56"english="Class Dismissed!"translation="S’ha acabat la classe!"explanation="This entire intermission section has a tone of a strict schoolteacher leading a small child."/>
<roomnameenglish="The Super Gravitron"translation="El supergravitró"explanation="An expanded version of the room at 53, 50"/>
<roomnameenglish="I Can't Believe You Got This Far"translation="No em puc creure que hagis arribat aquí"explanation="If you're playing No Death Mode, the room Prize for the Reckless has this roomname instead (the room is altered to make it possible to do this section without dying)"/>
<roomnameenglish="Imagine Spikes There, if You Like"translation="Si vols, imagina-t’hi punxes"explanation="If you're playing a time trial, the room Prize for the Reckless has this roomname instead (the room is altered to make it possible to do this section without dying)"/>
<!----->
<roomnameenglish="Rear Window"translation="La finestra indiscreta"explanation="Named after the Hitchcock film (The final stage has room names that suggest old black and white TV shows.)"/>
<roomnameenglish="On the Waterfront"translation="La llei del silenci"explanation="Named after the 1954 film (The final stage has room names that suggest old black and white TV shows.)"/>
<roomnameenglish="On the Vaterfront"translation="La llei del vilenci"explanation=""/>
<!----->
<roomnameenglish="The Untouchables"translation="Els intocables"explanation="Before it was a film, the Untouchables was a TV series in 1959 (The final stage has room names that suggest old black and white TV shows.)"/>
<roomnameenglish="Television Newsveel"translation="Noticiaris i Docvmentals"explanation="Refers to Television Newsreel. (The final stage has room names that suggest old black and white TV shows.)"/>
<roomnameenglish="Television Newsvel"translation="Notiviaris i Docvmentals"explanation=""/>
<roomnameenglish="The 9 O'Clock News"translation="Telenotícies vespre"explanation="(Second part of the final stage has references to Colour TV shows)"/>
<!----->
<roomnameenglish="Vwitched"translation="M de monocrom"explanation="Reference to early black and white sitcom Bewitched (The final stage has room names that suggest old black and white TV shows.)"/>
<roomnameenglish="Vwitvhed"translation="V de monovrom"explanation=""/>
<roomnameenglish="vVwivcvedv"translation="V ve movovrov"explanation=""/>
<roomnameenglish="Dvav Mvfvr Mdvvvv"translation="V ve vovvt"explanation=""/>
<roomnameenglish="Diav M for Mdrver"translation="V de vort"explanation=""/>
<roomnameenglish="Dial M for Murder"translation="M de mort"explanation="Named after the Hitchcock film (Second part of the final stage has references to Colour TV shows)"/>
<!----->
<roomnameenglish="Gvnsmoke"translation="La llei del revòlver"explanation="Gunsmoke was a black and white Western (The final stage has room names that suggest old black and white TV shows.)"/>
<roomnameenglish="Gvnsmove"translation="Va llei dev revòlver"explanation=""/>
<roomnameenglish="Gvnvmovevv"translation="Va vvei dev revòvvervv"explanation=""/>
<roomnameenglish="Gunvmove1vv6"translation="Va llei dev revòvver(1vv6v"explanation=""/>
<roomnameenglish="Vunsmoke 19v6"translation="Va llei dev revòlver (19v6)"explanation=""/>
<roomnameenglish="Gunsmoke 1966"translation="La llei del revòlver (1966)"explanation="Gunsmoke changed to colour in 1966 (Second part of the final stage has references to Colour TV shows)"/>
<!----->
<roomnameenglish="Please enjoy these repeats"translation="Us oferim les següents reposicions"explanation="This stage also has a number of rooms which are harder versions of easier challenges, like this one. (The final stage has room names that suggest old black and white TV shows.)"/>
<roomnameenglish="Please envoy theve repeats"translation="Vs oferim les següents revosicions"explanation=""/>
<roomnameenglish="Plse envoy tse rvpvas"translation="Vsovriv ves vevüevtv revovivvns"explanation=""/>
<roomnameenglish="Vl envoy te rvevs"translation="Vsvviv vevv evevtvevovvs"explanation=""/>
<roomnameenglish="Vv evo tv vevs"translation="Vsvv vevv vvevvvoviv"explanation=""/>
<roomnameenglish="In the Margins"translation="Dins dels marges"explanation="Not sure if this has a TV show reference, might just be meant literally (Second part of the final stage has references to Colour TV shows)"/>
<!----->
<roomnameenglish="Try Jiggling the Antenna"translation="Prova de moure una mica l’antena"explanation="(The final stage has room names that suggest old black and white TV shows.)"/>
<roomnameenglish="Try Viggling the Antenna"translation="Prova de movre vna miva l’antena"explanation=""/>
<roomnameenglish="Vvavvnvs vvtv"translation="Va vovva vev vev"explanation=""/>
<roomnameenglish="Veavvn's Gvte"translation="Va povta dev vel"explanation="Named after the 1980's film (Second part of the final stage has references to Colour TV shows)"/>
<roomnameenglish="Heaven's Gate"translation="La porta del cel"explanation="Named after the 1980's film (Second part of the final stage has references to Colour TV shows)"/>
</roomnames_special>
Some files were not shown because too many files have changed in this diff
Show more