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 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
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.
Okay, so basically here's the include layout that this game now
consistently uses:
[The "main" header file, if any (e.g. Graphics.h for Graphics.cpp)]
[blank line]
[All system includes, such as tinyxml2/physfs/utfcpp/SDL]
[blank line]
[All project includes, such as Game.h/Entity.h/etc.]
And if applicable, another blank line, and then some special-case
include screwy stuff (take a look at editor.cpp or FileSystemUtils.cpp,
for example, they have ifdefs and defines with their includes).
Including a header file inside another header file means a bunch of
files are going to be unnecessarily recompiled whenever that inner
header file is changed. So I minimized the amount of header files
included in a header file, and only included the ones that were
necessary (system includes don't count, I'm only talking about includes
from within this project). Then the includes are only in the .cpp files
themselves.
This also minimizes problems such as a NO_CUSTOM_LEVELS build failing
because some file depended on an include that got included in editor.h,
which is another benefit of removing unnecessary includes from header
files.
This removes around megabyte from the binary, so a stripped -Og binary
went from 4.0 megabytes to 2.9 megabytes, and an unstripped -O0 binary
went from 8.1 megabytes to 7.1 megabytes, which means I can now finally
upload an unstripped -O0 binary to Discord without having to give money
to Discord for their dumb Nitro thing or whatever.
Looks like coins were basically a scrapped mechanic, although I'm not
sure what these attributes were for. I guess counting the number of
coins in each room? But why, when you can just make a function to count
them automatically? Whatever.
map.contents always has 1200 tiles in it, there's no reason it should be
a vector.
This is a big commit because it requires changing all the level classes
to return a pointer to an array instead of returning a vector. Which
took a while for me to figure out, but eventually I did it. I tested to
make sure and there's no problems.
Since translucent roomname backgrounds were introduced in
TerryCavanagh/VVVVVV#122, it exposes one glaring flaw with the game that
until now has been kept hidden: in rooms with room names, the game
cheapens out with the tile data and doesn't have a 30th row, because the
room name would hide the missing row. As a result, rooms with room names
have 29 rows instead of 30 to fill up the entire screen. And it looks
really weird when there's nothing but empty space behind the translucent
room name background.
To remedy this, I added one row to each room with a room name in the level.
First, I had to filter out all the rooms with no room names. However, that's
actually all contained in Otherlevel.cpp, the Overworld, which contains 221
rooms (8 of which are the Secret Lab, 6 more of which are the Ship, so 207 are
the actual Overworld, right? Wrong, 2 of those Overworld no-roomname rooms are
in the Lab, so there are actually 205 Overworld rooms). The remaining level
data files all contain rooms with room names.
But the process wasn't that easy. I noticed a while ago that each room
contains 29 `tmap.push_back()`s, one for each row of the room, and each row is
simply a string containing the 40 tiles for that row, concatenated with
commas.
However, I decided to actually check my intuition by doing a grep on each
level file and counting the number of results, for example `grep 'push_back'
Labclass.cpp | wc -l`. Whatever number comes out should be divisible by 29.
That particular grep on Labclass.cpp returns 1306, which divided by 29 is 45
with a remainder of 1.
So what does that mean? Does that mean there's 45 rooms each, and 1 leftover
row? Well, not exactly. The extra row comes from the fact that Outer Space has
30 rows instead of 29. Outer Space is the room that comes up when the game
finds a room is non-existent, which shouldn't happen with a properly-working
game, except in Outside Dimension VVVVVV. In fact, each level file has their
own Outer Space, and every single Outer Space also has 30 rooms. So really,
this means there are 44 rooms in the Lab and one Outer Space room. (Well, in
reality, there are 46 rooms in the Lab, because 2 of them use the Outside
tileset but have no room names, so they're stored in Otherlevel.cpp instead.)
We find the same result for the Warp Zone. `grep 'push_back' WarpClass.cpp |
wc -l` returns 697, which is 24 remainder 1, meaning 23 rooms of 29 rows and 1
room of 30 rows, which corresponds with 23 rooms in the Warp Zone and one
Outer Space room.
However, Outside Dimension VVVVVV + Tower Hallways and Space Station 1 and 2
are both odd curiosities. Finalclass.cpp contains Outside Dimension VVVVVV,
(which is Intermission 1 and 2 and the Final Level), but also the Tower
Hallway rooms, i.e. the auxiliary Tower rooms that are not a part of the main
tower. Spacestation2.cpp contains both Space Station 1 and 2, so don't be
deceived by the name.
`grep 'push_back' Finalclass.cpp | wc -l` returns 1597, which is actually 55
remainder 2. So... are there two rooms with 30 rows? Yes, in fact, The
Gravitron and Outer Space both contain 30 rows. So there are actually 55 rooms
stored in Finalclass.cpp (not including the minitowers Panic Room and The
Final Challenge), 54 rooms of actual level data and one Outer Space room, and
breaking down the 54 rooms even further, 51 of them are actually in Outside
Dimension VVVVVV and 3 of them are Tower Hallways. Of the 51 Outside Dimension
VVVVVV rooms, 14 of those are Intermission 1, 4 of them are Intermission 2,
and the rest of the 33 rooms are the Final Level (again, not including the
minitowers).
`grep 'push_back' Spacestation2.cpp | wc -l` returns 2148, which is 74
remainder 2. Are there two rooms with 30 rows again? No; one of those counted
2148 rows is a false-positive, because there's an if-else in Prize for the
Reckless that replaces the row with spikes with a row without spikes if you
are in a time trial or in No Death Mode. So there's 73 rooms in Space Station
1 and 2, and one Outer Space room.
With all this in mind, I decided to duplicate the current last row of each
room, the 29th row, to add a 30th row. However, I wasn't going to do this
automatically! But neither was I going to write some kludge-y code to parse
each nightmare of a level file and duplicate the rows that way.
Enter: Vim macros! (Er, well, actually, I use Neovim.) I first did
`/push_back`, so that pressing `n` would keep going to the next `push_back` in
the file. Then I went to the 29th row of the first room in the file, did a
`Yp`, and then started my macro with `qq`. The macro went like this: `30nYp`,
which is simply going to the 29th row of the next room over and duplicating
it. And that's all there was to it. However, I had to make sure that (1) my
cursor was before the `push_back` on the line of the 29th row of the room, and
(2) that I didn't skip rooms, both of which were problems I encountered when
pressing Ctrl+Z a given invocation of the macro (the Ctrl+Z is just a
metaphor, you actually undo by typing `u` in Vim). And also I had to make sure
to be careful around the extra lines of `push_back`s in Prize for the Reckless
and The Gravitron, and make sure I didn't run past the end of the file and
loop back around. Thankfully, all Outer Space rooms are at the end of each
file.
But first, I had to increase the number of rows drawn in Graphics.cpp by 1 in
order to compensate for this, and do the same when reading the tile data in
Map.cpp. I had to change fillcontent(), drawmap(), drawfinalmap(),
drawtowermap(), and drawtowermap_nobackground(). Funnily enough, the tower
functions already used 30 rows, but I guess it's an off-by-one due to the
camera scrolling, so they now draw 31 rows each.
Then, I went in-game to make sure that the row behind each room name looked
fine. I checked EVERY single room with a room name. I turned on invincibility
mode and added a temporary line to hardreset() that always turned on
game.nocutscenes for a smoother playtesting experience. And to make sure that
rooms which have entirely empty bottom rows actually still have 30 rows,
instead of having 29 and the game assuming that the 30th row was empty
(because that sounds like it could lead to Undefined Behavior), I added this
temporary debugging line to the start of mapclass::fillcontent():
printf("(%i,%i) has %i rows\n", game.roomx, game.roomy, (int) tmap.size());
Everywhere I checked - and I made sure to check all rooms - every room had 30
rows and not 29 rows.
Unfortunately, some rooms simply couldn't be left alone with their 29th row
duplicated and had to be manually edited. This was because the 29th row would
contain some edge tiles because the player would be able to walk somewhere on
the 28th, 27th, and 26th rows, and if you duplicated said edge tiles behind
the room name, it would look bad.
Here's a list of rooms whose 30th rows I had to manually edit:
- Comms Relay
- The Yes Men
- Stop and Reflect
- They Call Him Flipper
- Double-slit Experiment
- Square Root
- Brought to you by the letter G
- The Bernoulli Principle
- Purest Unobtainium
- I Smell Ozone
- Conveying a New Idea
- Upstream Downstream
- Give Me A V
- $eeing Dollar $ign$
- Doing Things The Hard Way
- Very Good
- Must I Do Everything For You?
- Now Stay Close To Me...
- ...But Not Too Close
- ...Not as I Do
- Do Try To Keep Up
- Whee Sports
- As you like it
This is actually a strange case where it looked bad because of the 29th
row, instead of the 30th row, and I had to change the 29th row instead of
the 30th row to fix it.
- Maze With No Entrance
- Ascending and Descending
- Mind The Gap
Same strange case as "As you like it" (it's the 29th row I had to change
that was the problem, not the 30th).
- 1950 Silverstone Grand V
- The Villi People
I found that Panic Room and The Final Challenge also looked strange behind the
roomname background, but I can't do much about either because towers' tile
data wrap around at the top and bottom, and if I added another row to either
it would be visible above the room name.
I've considered updating the development editors with these new level tiles,
but I decided against it as the development editors are already pretty
outdated anyway.
Instead, they're all stored in a constant int array.
I made sure The Gravitron still has 30 rows just like Outer Space,
though I don't think it matters.
It's treated like a bool anyway, so might as well make it one.
This also necessitates updating every single instance where it or an
element inside it is used, too.
This changes something like UtilityClass::String to help.String,
basically. It takes less typing this way, and is a neat effect of having
global args actually be global variables.
This commit removes all global args from functions on the entityclass
object, and updates the callers of those functions in other files
accordingly (most significantly, the game level files Finalclass.cpp,
Labclass.cpp, Otherlevel.cpp, Spacestation2.cpp, WarpClass.cpp, due to
them using createentity()), as well as renaming all instances of 'dwgfx'
in Entity.cpp to 'graphics'.
Following discussion on TerryCavanagh/VVVVVV#153, I suggested that
instead of reverting my M&P guards from TerryCavanagh/VVVVVV#124 (which
would only revert it for The Final Level, The Lab, Overworld, and The
Tower, leaving Space Station 1 & 2 and The Warp Zone alone which could
potentially cause the same problem that motivated
TerryCavanagh/VVVVVV#153), we should initialize the map data with 0s
instead.
This is to make sure that the room data of those areas are not
distributed with the M&P binary, even if they are already inaccessible
because of other M&P ifdefs (I tested).