Wednesday, July 31, 2013

Mostly parsing and magics

Let me start this post with our usual screenshot!
As you may see, there are a lot of changes and improvements since my last post. I'll tell you the story of the last week in the followings, so prepare for a smaller wall of text, a lot had happened! ;)

The first and most obvious thing is that I took many ScummVM members' advices and doubled the screen height again. We all agreed that it will be it's final size. The algorithm was pretty easy, I only had to update Graphics::refreshScreen() a bit. (Oh, and I renamed the class Graph to Graphics. It was sev's idea and I agree with him. Now it reflects much more the purpose of the class, and I added so much things to it during the past days that it lost all of its connections to the Graph unit in Pascal, so there's really no point in keeping the old name.) So, the new refreshScreen():
void Graphics::refreshScreen() {
 // These cycles are for doubling the screen height.
 ::Graphics::Surface picture;
 picture.create(kScreenWidth, kScreenHeight * 2, ::Graphics::PixelFormat::createFormatCLUT8());
 for (uint16 y = 0; y < picture.h / 2; y++)
  for (uint16 x = 0; x < picture.w; x++)
   for (byte j = 0; j < 2; j++) 
    *(byte *)picture.getBasePtr(x, y * 2 + j) = *(byte *)_surface.getBasePtr(x, y); 

 // Now we copy the stretched picture to the screen.
 g_system->copyRectToScreen(picture.pixels, picture.pitch, 0, 0, kScreenWidth, kScreenHeight * 2);
I think the comments make it pretty understandable. I still work with the original 640x200 resolution when loading pictures to the screen (If you check out graphics.h, you can see it for yourself in the form of kScreenWidth and kScreenHeight.), but when I show the actual output, I draw every line twice, so it's like we have a screen height of 400.

I also want to mention that I reworked loading of pictures a little bit in the form of loadPictureGraphic() and loadPictureRow(). The names of these functions came from here, referring to their behavior. Obviously the sooner one loads graphical data that uses "graphic-planar EGA data" as input and the latter one is for loading "row-planar EGA data". In the end, both produce a Surface object with the same format as output. This revision was needed because I had to use these two methods frequently and it made the whole thing much convenient since now Graphics::drawPicture() only accepts a Surface object and two coordinates, and draws the input picture (coming from one of the loading functions or simply by copying parts of the already drawn screen) to the given place. You can check out the current source code of these algorithms in graphics.cpp. I wouldn't describe them further here, because I talked about their operation in my previous posts a lot.

The next thing I spent a large amount of time with during the week was parsing. Since the original code was a bit hard to understand it took me more time to look through all of it than the actual coding, but finally it's done and working! :)
I introduced a new class, Parser (you can check this out here and here), which is practically a rework of Basher. One of my further plans is to completely get rid of Basher and move everything from it to Parser after renaming them. The "funny" thing is that the actual parsing is not done here (yet), but in Acci::parse(), and Acci::do_that() takes actions based on the result of the parsing (run animations, move characters, etc.). Parser now is only for processing the keyboard input and the handling of the visualization of it in a little command line under the main picture and over the toolbar. (You can see it in the screenshot at the top of the post, I wrote "get up" into it.) It's also my plan to move at least these two functions from Acci to Parser, and even more data, if I find out that they are only connected to parsing.
I wouldn't insert any code samples connected to parsing here now, since parse.h and parse.cpp contains quite readable code in my opinion, and Acci::parse() and Acci::do_that() is in such a bad condition that I don't want to advertise them at all. (:D) Let it be enough that they are called by Parser::handleReturn(), and they work properly. But a certain rework will be needed soon if I want to keep my code readable... Anyway, if you want to check out them in their current forms, here you go.

Also, not only the text input is working now (By using that you can wake up Avvy typing in "wake" or "wake up" and get him out of bed typing "stand", "stand up", or "get up". Other commands may don't work properly or don't work at all at the moment.), but you can move Avvy around in the first room of the game using the cursor keys or the home/end/page up/page down buttons executing diagonal movements with them. You can also control him with the numeric keyboard and stop him by hitting the numeric key '5' or hitting the direction key he is facing right now again.

We arrived to the second greater part of my last week's work: the MAGICS!
No, not turning people into toads or summoning a demon from Burning Hell using ScummVM, nothing like that. It's the name of the system in Avalanche which forbids the characters to cross the border of the screen and walk right on the toolbar for example. Let me illustrate it with the help of two screenshots!

As you may have noticed checking out Graphics, I added a new Surface object to the engine named _magics. This is the one which stores the image to the left. The white lines on it means the edges of the screen what Avvy (or any NPC) cannot cross.
They are drawn to this Surface in Lucerna::draw_also_lines(), using the data stored in Gyro::lines[] - which's content is loaded in Lucerna::load_also() amongst many other room-related data. If you'd like to read more about this matter, you can always check out the wiki pages of the original author.
So basically the algorithm in triptype::walk() is the following: we check if the next step of the character would cause him to cross a line on _magics and if it would, we simply stop the character.
Vigilant readers may have noticed the blue colored rectangle on the bottom left of the pictures, where the door is. It marks a different kind of "magic", which means that if we cross these lines, we want to leave the room.

My plans for the following weeks are making the speech bubbles (the so-called scrolls), the drop-down menu on the top and the transportation between rooms work. The drawing system will also need a certain rework, since now it can handle only one sprite per screen.


  1. Your progress continues to impress me.

    If I'd been doing the magics now, I'd have stored them as an array of (start coordinate, end coordinate, colour) tuples rather than drawing them on a surface, and then used the usual algorithm to check for an intercept on each step. But then, I was young and foolish and had to work a lot of things out from scratch. :)

    1. Good idea, thank you! :) I'll might give it a go later.

  2. (Oh, one thing I forgot to add: in theory Trip can handle something like five sprites on a screen, but in practice there are never more than two sprites visible at any point. So two is all you need to be able to handle.)

    1. Okay, great, thanks for the information! :)