Pyrdacor / Ambermoon

Resources for the incredible Amiga game Ambermoon
90 stars 8 forks source link

Map Events and Scripting #21

Open csinkers opened 2 years ago

csinkers commented 2 years ago

Hey, thought I'd put some notes here based on my investigations into Albion, hopefully they'll be useful.

NpcFlags

Bit 6 of the NPC flags in Albion acts as 'noclip', i.e. NPCs ignore walls. Maybe one of the unknown Ambermoon flags does the same.

Also, in Albion there seems to be a concept of "version 1" and "version 2" NPC data. Which one is used depends on a map flag (0x2000, if the first two bytes of the map data are interpreted as a 16-bit flags enum). Might be a candidate for the unknown flag 5 that's always set in Ambermoon (all maps except some unused test maps in Albion have 0x2000 set).

In Albion, when the flag is not set (only test maps + my own investigations):

When it is set (every real map):

Map Events

These aren't actually in a linked list structure. Because 'Query' events (the name "Query" comes from debug strings left in the Albion exe, in this repo they're referred to as Condition events, type number 0xD) will jump to the two-byte event index at offset 0x6 when the condition is false and to the index stored at offset 0x8 when true, the appropriate data structure is actually a directed graph of maximal out-degree 2. Because of this, any arbitrary programming control flow structure can be represented, making things much more complicated.

To make Albion modding easier I've been working on a higher-level scripting language for these events, along with a compiler and decompiler so the directed graphs encoded in the map and event-set data can be converted into nice human-readable scripts (and back again). The language itself is quite simple: there's no type system, it's really just some control flow structures put on top of the Albion script events, things like if-else, while, goto etc. As an example here's a decompiled Albion script involving conditions and looping:

{
    Chain35:
    disable_event_chain Map.Kenget2D 35 1 0
    sound Sample.Hissing 0 0 90 0
    map_text MapText.Kenget2D 187 ; "Suddenly a call echoes out of a concealed side passage!"
    do_script Script.Unknown25
    while (!(prompt_player MapText.Kenget2D 190)) {
        map_text MapText.Kenget2D 191 ; "{INK 002} \"You want the honor of combat? Very good!\" ^ ^ {INK 001} The Kenget Kamulos attack!"
        encounter 170 7
        if (result) {
            disable_npc 33
            disable_npc 34
            disable_npc 35
            disable_npc 36
            wipe 0
            do_script Script.Unknown26
        } else {
            map_text MapText.Kenget2D 193 ; "The Kenget Kamulos attack again immediately!"
        }
    }
    pause 30
    map_text MapText.Kenget2D 194 ; "\"Bring them to the prison area!\""
    teleport Map.KengetPrison 4 36 North 255 0
}

The code for the scripting language compiler & decompiler is in https://github.com/csinkers/ualbion/tree/master/src/Scripting - perhaps it could be useful for Ambermoon as well. The only dependencies of the scripting sub-project are Superpower (a C# parsing library) and a couple of fairly simple types in the UAlbion.Api project (IEvent, IEventNode, IBranchNode are the main ones), so it might be feasible to fork it and replicate the required interfaces from UAlbion.Api, although it does also require having a system for reliably round-tripping events between binary and text forms, and that the text form of the events adhere to certain conditions.

In its current state it can't handle every possible control flow structure, but it does handle all the ones that occur in Albion's data files, so hopefully it would be general enough.

I'll keep an eye out for any other correspondences between data structures, but it looks like most of the parts that are the same in the two games have already been decoded, and the unknown parts don't line up.

Pyrdacor commented 2 years ago

Hi Cam. Thanks for your valuable input. At the moment I decode every single map event bit of Ambermoon. I am almost done with it.

The branching of the condition/query event is known. The same is possible with the dice roll event (0xF) and the question/decision popup event (0x13). Moreover the door and chest events (0x2 and 0x3) can specify an event index for the "unlock failed" situation. It is used when lockpicking or trap disarming fails.

So in total there are 5 possible branching event types in Ambermoon.

Condition/query event sub-type 9 stands for "last event result". This way you can branch dependent on won battles, correct riddlemouth solutions or fully looted treasures.

The scripting language looks nice. I wrote a small console app to edit the event chains of maps and NPC of Ambermoon. But as you mentioned it can get quite difficult to understand if branching is involved.

I will have a closer look at the NPC flags too. But Albion uses an extended version of the data structures of Ambermoon, so this might be specific to Albion.