Summary of GSoC 2018 — The Immortal

Overview

During the last three months I worked on porting The Immortal.
Unfortunately, I have fallen ill last month and are still recuperating so I haven’t got much work done since. Although GSoC is ending, it doens’t mean work on The Immortal will cease as well. I will keep working on it over the next month to make up for my sick days.

Github Repository
Blog GSoC 2018

DONE

  • Dialog parsing and rendering
  • Audio conversion (EA BIN to MIDI)
  • Rendering
  • Inventory
  • Animation definitions
  • Map parsing, rendering, and room definitions

TODO

  • Animation system
  • Collision detection
  • Object/Actor interaction
  • Fighting
  • AI

Those are the big ones left until the game is in a playable state. I’m sorry for everyone that was looking forward to play the game at the end of this GSoC period and hope to make up for it by the end of September with a PR. I want to thank my mentors Arnaud Boutonné, Thierry Crozat, the whole ScummVM team and Google for creating the Summer of Code in the first place.

 

Week 10

I’m sorry for the lack of progress and apologize to everyone that is eagerly looking forward to playing the game. Fortunately, after a short stay at the hospital I’m feeling better now and can resume work on the project.

Week 9 — Give me some of that water.

No real progress as I have been feeling really sick the last couple of days. A sip from that fountain water would be really nice right now…
The camera tracking of the player is still a bit wonky but once that is ironed out, object iteraction and triggers are next as was planned in last week’s post.

Week 8 — Animation and Controls

(Source: https://www.youtube.com/watch?v=AUu9c4qiMGE)

  • Rewriting animation handling
  • Avatar (controls and animations)
  • Inventory

Even the simplest thing can turn out to be a big time drain.

I encountered mismatches in the mapping for sprites and their actual position in the files before and assumed that some change from version to version. Apparently, they changed the file layout and compression mid-development and added a lookup table to remap sprite packs and files to how they (presumably) were originally. I’m glad it cost me only a day of manually jumping around in tables and I didn’t end up with more work than necessary.

Seeing the wizard casually paddle out of bounds on his barrel is more than worth it though.

The inventory is almost done (except for using items of course). Either text rendering needs to be refactored so it can be used outside of dialogs or rewrite the inventory so it fits the structure of a dialog. I will update the GIF tomorrow once it’s fixed.

Next

  • Update viewport camera to follow wizard
  • Render static objects (chests, torches, …)
  • Collision detection
  • Interaction with objects (pick up items from bodies, chests, …)

Week 7 — Passwords and Plumbing

(Source: https://www.youtube.com/watch?v=iVlRMM5ctWI)

The dialog implementation is now in a good state with all 199 dialog texts refactored, icons being rendered at where they should, the password interface and exit dialog working, and most bugs that kept annoying me out of the way.

Also map rendering is now clipped to the viewport, the foundation for level loading stands, and I ripped out the old animation system and with it parts of how resources are loaded/stored to rewrite it closer to the original and thus making my life easier down the road.

Passwords are called ‘certificates’ in the game, so don’t be surprised when I use those terms interchangeably. While the input dialog works when selected ‘no’ on the new game screen, there are a few inconsistencies with decoding the password so it hasn’t been implemented yet.

It encodes values in hexadecimal, has a length of 13, and stores the following values:

pos description
0 – 3 checksum value
4 hit points
5 game level
6 – 7 game flags (low/hi)
8 slash speed
9 – 10 inventory items (bitfield)
11 – 12 gold

Next

After losing a bunch of work last week I reimplemented enough for working on the first level. The plan is to have our wizard moving around and static object animations like torch flames working so Mordamir can finally leap from his candle.

Week 6 — What are backups again?

No pretty pictures this week. Due to technical failure and mistakes on my part, a good chunk of my work resides in data nirvana now. Guess that’s why you shouldn’t mess with a running system too much and do backups…

All rooms, and objects for the first level are in and need to be included in the renderer.
In the coming days I will post an update on the situtation and how I plan to make up for the wasted days.

Week 5 — It’s Alive!

Overview

  • Map rendering working
  • Remaining Dialog texts ported

Details

Your image looks corrupted and you definitely ruled out pointer errors? Well, it’s a pointer error anyway.
Or endianess. Or both together…
It took a while to realize that subtile indices are stored as uint16BE but now I AM BACK IN BUSINESS!

Next

This weekend I want to add viewport clipping and map masking so you don’t see the whole map ingame like in the above image. There’s also some tile specific code left to implement (like the water in the sewers) but that’s for another time. Finally we can get going on implementing the actual gameplay \o/

Week 4 — Show me the World

Overview

  • Map rendering
  • Improving dialog handling

Unfortunately the map rendering kept breaking so there’s no video of it right now. While there are improvements in other areas like the dialog handling, the map renderer blocked me for most of the week.
Below I describe the structure of the MAZE.CMP file that stores static background tiles and how it is used.

Details

struct Map { // MAZE.CMP
    byte tileIndex[128][20]
    byte subTileIndex[146][64]
    byte bitmap[1832][32]
}

Compared to sprites from animation files (discussed in Week 2) the static background is composed of tiles from MAZE.CMP. Those tiles in turn are made of subtiles of 8×8 to save space by assembling the bigger tiles of the smaller ones. So the tileIndex are offsets into subTileIndex that again are offsets to the 8×8 pixel data in bitmap.

On every frame an indexMap is generated of the currently visible subtiles. The reason is not only for caching but for clipping tiles (that’s what kAnimationShadow* are for) and general tile manipulation depending on game state.

34×18 tiles are shown at a time on screen. There will be overdraw as the viewport is only 256×128 but it doesn’t matter as the video buffer is 320×200 and it is clipped off by the frame border in the end. Currently my implementation draws the frame border first for “clearing” the screen as it was convenient to just blit the fullscreen image without the need for transparency.

Next Week

The most urgent feature right now is renderering the map. Not only does it help to visually convey the progress better but other features like player movement, rendering of NPCs, Objects, animation of static objects like torches, collision, … depend on it. Besides that, the definitions for the all objects, … will need to be ported over as well.

Week 3 — Game intro running

Overview

  • Text rendering
  • Basic Dialog implementation for intro
  • Logic abstraction

The porting process is tougher than I had anticipated. I described my problems with the animations in Week 2 and continued with correctly positioning sprites in the dialog. Fortunately I [found my mistake](link to Renderer::drawSprite pos calc) and dialogs not only for the intro, but in general will be solved soon.

Details

The logic differentiates between different states like showing a dialog, the game, a cutscene, and so on. This helps the logic to know when to update what so that for example you are not slain while still picking up items from a chest. link

The characters for text rendering are stored in general2.anm that use the ASCII value as an offset to access the sprite in the animation (link to Dialog::update default case). The dialog strings contain information directly for fadein/out, what type of dialog it should be (Yes/No, Ok, multipage, immediate) and so on.

In general the implementation is quite simple. The engine iterates over every char one after another and either executes the ‘meta char’ (e.g. ‘|’ for fade in text) or renders the sprite for the character. When the null terminator is reached and no further string is to be loaded, the dialog rendering terminates and depending on if the dialog expects user input (password, yes/no, ok) the logic either waits on a keypress before switching back to its ‘game state’ and continues where the player left off.

Next Week

Judging from my current speed, I hope to get our hero making its first steps in the first level and ironing out the remaining dialog bugs.

Week 2 — Animation

I picked the animations above randomly but I just realized how they depict the development process so far.

Coffee fueled nights, excruciating pain bursting my skull (most of the time because of dumb mistakes I miss for hours), but you must press on! 😉

Overview

  • .anm file support 1
  • Mapping animations 1 2
  • Render support for animations

Details

All sprites are stored in .anm files, divided intro multiple sprite chunks that either form an animation or just an accumulation of sprites like inventory icons or characters.
There is no meta information that would help identify what frames form an animation in cases where several animations are stored in one sprite chunk (blood splatter effects for example). So i manually mapped them what can be quite error prone, especially as some animations are not used in the game and seem to differ from version to version.
The DOS/Genesis/NES version have a battle scene compared to other versions where the fighting happens on the map. Some of the sprites are still available but the animations seem to be incomplete.

struct AnmFile {
    struct AnimationHeader {
        u16 offsetToSpriteOffsets
        u16 numFrames
        u16 centerX
        u16 centerY
    } [numOfAnimationsInFile]
    
    u16 offsetToSprites[header.numFrames]	
    
    struct Sprite {
        byte x
        byte y
        byte width
        byte height
        byte remap
        byte widthPerLine[sprite.height]
        byte pixelPosOffset[sprite.height]
        byte pixelData[] // one nibble per pixel
    } []
}

Next Week

Until the end of this week, I will implement the game intro what will encompass extending the game logic and dialog implementation.