Changeset 3d27dc6 in Readingame


Ignore:
Timestamp:
Aug 18, 2023, 10:58:35 PM (10 months ago)
Author:
PulkoMandy <pulkomandy@…>
Branches:
main
Children:
3d333fd
Parents:
bd5d630
Message:

Start making the game run

Implemented text rendering, buttons and hyperlinks.

Several opcodes not interpreted yet.

Global buttons are missing.

Files:
2 added
2 edited

Legend:

Unmodified
Added
Removed
  • build.sh

    rbd5d630 r3d27dc6  
    11#!sh
    22
    3 g++ main.cpp -o main -lbe
     3g++ -gdwarf-3 *.cpp -o main -lbe
  • main.cpp

    rbd5d630 r3d27dc6  
    55 */
    66
     7#include "HyperTextView.h"
     8
    79#include <Application.h>
     10#include <Button.h>
    811#include <ByteOrder.h>
    912#include <Directory.h>
    1013#include <Entry.h>
    1114#include <File.h>
     15#include <GroupLayout.h>
     16#include <LayoutBuilder.h>
    1217#include <String.h>
     18#include <TextEncoding.h>
     19#include <TextView.h>
    1320#include <Window.h>
    1421
     
    151158}
    152159
    153 int prettyPrintEscape(const std::string& message, int cursor)
    154 {
    155         if (message[cursor + 1] == 'o') {
    156                 printf("\n" BOLD "OK");
    157                 int v = 0;
    158                 int i = 2;
    159                 while (isdigit(message[cursor + i])) {
    160                         v = v * 10 + message[cursor + i] - '0';
    161                         i++;
    162                 }
    163                 if (i != 0)
    164                         printf("[Hook %x]", v);
    165                 printf(NORMAL "\n");
    166                 return i;
    167         } else if (message[cursor + 1] == '!') {
    168                 printf(BOLD "APPEND" NORMAL "\n");
    169                 return 2;
    170         } else {
    171                 // TODO unknown escape sequence
    172                 printf("%c", message[cursor]);
    173                 return 1;
    174         }
    175 }
    176 
    177 int prettyPrintHyperlink(const std::string& message, int cursor)
    178 {
    179                 int v = 0;
    180                 int i = 1;
    181                 while (isdigit(message[cursor + i])) {
    182                         v = v * 10 + message[cursor + i] - '0';
    183                         i++;
    184                 }
    185 
    186                 printf(BOLD "[Hook %x]", v);
    187 
    188                 while (message[cursor + i] != ']') {
    189                         printf("%c", message[cursor + i]);
    190                         i++;
    191                 }
    192 
    193                 printf(NORMAL);
    194                 return i + 1;
    195 }
    196 
    197 void prettyPrint(const std::string& message)
    198 {
    199         size_t cursor = 0;
    200         while (cursor < message.size()) {
    201                 switch(message[cursor]) {
    202                         case '\\':
    203                                 cursor += prettyPrintEscape(message, cursor);
    204                                 break;
    205                         case '[':
    206                                 cursor += prettyPrintHyperlink(message, cursor);
    207                                 break;
    208                         case '@':
    209                         case '$':
    210                                 // @ Gender mark (print an e if the player identifies as female)
    211                                 // $ Plural mark (print an s if the counter is plural)
    212                                 printf(BOLD "%c" NORMAL, message[cursor]);
    213                                 cursor++;
    214                                 break;
    215                         default:
    216                                 printf("%c", message[cursor]);
    217                                 cursor++;
    218                                 break;
    219                 }
    220         }
    221         printf("\n");
    222 }
     160const char* cp850_lookup[0x80] = {
     161        "Ç", "ü", "é", "â", "ä", "à", "å", "ç", "ê", "ë", "è", "ï", "î", "ì", "Ä", "Å", "É", "æ", "Æ",
     162        "ô", "ö", "ò", "û", "ù", "ÿ", "Ö", "Ü", "ø", "£", "Ø", "×", "ƒ", "á", "í", "ó", "ú", "ñ", "Ñ",
     163        "ª", "º", "¿", "®", "¬", "½", "¼", "¡", "«", "»", "░", "▒", "▓", "│", "┤", "Á", "Â", "À", "©",
     164        "╣", "║", "╗", "╝", "¢", "¥", "┐", "└", "┴", "┬", "├", "─", "┼", "ã", "Ã", "╚", "╔", "╩", "╦",
     165        "╠", "═", "╬", "¤", "ð", "Ð", "Ê", "Ë", "È", "ı", "Í", "Î", "Ï", "┘", "┌", "█", "▄", "¦", "Ì",
     166        "▀", "Ó", "ß", "Ô", "Ò", "õ", "Õ", "µ", "þ", "Þ", "Ú", "Û", "Ù", "ý", "Ý", "¯", "´", "\u00AD",
     167        "±", "‗", "¾", "¶", "§", "÷", "¸", "°", "¨", "·", "¹", "³", "²", "■", "\u00A0"
     168};
    223169
    224170class Room {
     
    229175
    230176std::vector<Room> gRooms;
     177std::vector<uint8_t> gVar8;
     178std::vector<uint16_t> gVar16;
     179
     180uint8_t gCurrentRoom;
     181uint8_t gCurrentScreen;
     182uint8_t gTrigger;
    231183
    232184// TODO objects
     
    236188// TODO initial room
    237189// TODO other things from the init script
     190
     191class GameWindow: public BWindow
     192{
     193        public:
     194                GameWindow()
     195                        : BWindow(BRect(40, 40, 500, 400), "Readingame", B_DOCUMENT_WINDOW,
     196                                B_AUTO_UPDATE_SIZE_LIMITS | B_QUIT_ON_WINDOW_CLOSE)
     197                        , fDecoder("cp-850")
     198                {
     199                        fMainText = new HyperTextView("main text", B_WILL_DRAW);
     200
     201                        // TODO making it non-editable while having custom insets breaks the layout somehow?
     202                        fMainText->MakeEditable(false);
     203                        fMainText->MakeSelectable(false);
     204                        //fMainText->SetInsets(10, 10, 10, 10);
     205
     206                        fInlineButton = new BButton("button", NULL);
     207
     208                        BLayoutBuilder::Group<>(this, B_VERTICAL)
     209                                .SetInsets(0, 0, 0, B_USE_WINDOW_SPACING)
     210                                .Add(fMainText, 999)
     211                                .AddGroup(B_HORIZONTAL)
     212                                        .AddGlue()
     213                                        .Add(fInlineButton)
     214                                        .AddGlue()
     215                                .End()
     216                                .AddGlue(0)
     217                        .End();
     218                }
     219
     220                void Show() override
     221                {
     222                        BWindow::Show();
     223                        Lock();
     224                        fInlineButton->SetLowColor(make_color(255, 128, 255));
     225                        fMainText->SetViewColor(make_color(196, 255, 255));
     226                        fInlineButton->SetTarget(be_app);
     227                        Unlock();
     228                }
     229
     230                void SetMainText(const std::string& text)
     231                {
     232                        Lock();
     233                        fInlineButton->Hide();
     234                        fMainText->SetText("");
     235
     236                        BString decoded;
     237                        PrettyPrint(text, decoded);
     238
     239                        text_run_array runs;
     240                        runs.count = 1;
     241                        runs.runs->font = be_plain_font;
     242                        runs.runs->offset = 0;
     243                        runs.runs->color = make_color(0, 0, 0);
     244                        fMainText->Insert(decoded, &runs);
     245                        fMainText->Invalidate();
     246                        Unlock();
     247                }
     248
     249                void PrettyPrint(const std::string& message, BString& decoded)
     250                {
     251                        size_t cursor = 0;
     252                        while (cursor < message.size()) {
     253                                switch(message[cursor]) {
     254                                        case '\\':
     255                                                cursor += HandleEscape(message, cursor + 1);
     256                                                break;
     257                                        case '[':
     258                                        {
     259                                                text_run_array runs;
     260                                                runs.count = 1;
     261                                                runs.runs->font = be_plain_font;
     262                                                runs.runs->offset = 0;
     263                                                runs.runs->color = make_color(0, 0, 0);
     264                                                fMainText->Insert(decoded, &runs);
     265                                                decoded = "";
     266                                                cursor += HandleHyperlink(message, cursor);
     267                                                break;
     268                                        }
     269                                        case '@':
     270                                        case '$':
     271                                                // @ Gender mark (print an e if the player identifies as female)
     272                                                // $ Plural mark (print an s if the counter is plural)
     273                                                printf(BOLD "%c" NORMAL, message[cursor]);
     274                                                cursor++;
     275                                                break;
     276                                        case '_':
     277                                                // Used as a non-breakable space
     278                                                decoded += "\u00A0";
     279                                                cursor++;
     280                                                break;
     281                                        default:
     282                                                if (message[cursor] >= 0)
     283                                                        decoded += message[cursor];
     284                                                else
     285                                                        decoded += cp850_lookup[(message[cursor] - 0x80) & 0xFF];
     286                                                cursor++;
     287                                                break;
     288                                }
     289                        }
     290                        decoded += '\n';
     291                }
     292
     293                int HandleHyperlink(const std::string& message, int cursor)
     294                {
     295                        BString linkText;
     296
     297                        int v = 0;
     298                        int i = 1;
     299                        while (isdigit(message[cursor + i])) {
     300                                v = v * 10 + message[cursor + i] - '0';
     301                                i++;
     302                        }
     303
     304                        while (message[cursor + i] != ']') {
     305                                if (message[cursor + i] >= 0)
     306                                        linkText += message[cursor + i];
     307                                else
     308                                        linkText += cp850_lookup[(message[cursor + i] - 0x80) & 0xFF];
     309                                i++;
     310                        }
     311
     312                        text_run_array single_run;
     313                        single_run.count = 1;
     314                        single_run.runs->offset = 0;
     315                        single_run.runs->font = be_plain_font;
     316                        single_run.runs->color = make_color(0, 0, 196);
     317                        fMainText->InsertHyperText(linkText, new HyperTextAction(v), &single_run);
     318
     319                        return i + 1;
     320                }
     321
     322                int HandleEscape(const std::string& message, int cursor)
     323                {
     324                        if (message[cursor] == 'o') {
     325                                fInlineButton->SetLabel("OK");
     326                                fInlineButton->Show();
     327                                int v = 0;
     328                                int i = 1;
     329                                while (isdigit(message[cursor + i])) {
     330                                        v = v * 10 + message[cursor + i] - '0';
     331                                        i++;
     332                                }
     333                                fInlineButton->SetMessage(new BMessage(v));
     334                                return i + 1;
     335                        } else if (message[cursor] == '!') {
     336                                printf(BOLD "APPEND" NORMAL "\n");
     337                                return 2;
     338                        } else {
     339                                // TODO unknown escape sequence
     340                                fprintf(stderr, "ERROR: unknown escape sequence %c", message[cursor]);
     341                                return 2;
     342                        }
     343                }
     344
     345        private:
     346                HyperTextView* fMainText;
     347                BButton* fInlineButton;
     348                BPrivate::BTextEncoding fDecoder;
     349};
     350
     351class Readingame: public BApplication
     352{
     353        public:
     354        Readingame()
     355                : BApplication("application/x-vnd.PulkoMandy.Readingame")
     356        {
     357        }
     358
     359        void ReadyToRun() override
     360        {
     361                // TODO check if we have a game loaded
     362                fMainWindow = new GameWindow();
     363
     364                // FIXME add the global buttons to the main window
     365
     366                BWindow* debugWindow = new BWindow(BRect(600, 40, 900, 200), "Readingame debugger",
     367                        B_DOCUMENT_WINDOW, B_AUTO_UPDATE_SIZE_LIMITS | B_QUIT_ON_WINDOW_CLOSE);
     368
     369                fMainWindow->Show();
     370                fMainWindow->CenterOnScreen();
     371
     372                // TODO: only if asked from the command line
     373                debugWindow->Show();
     374
     375                bool runNextLine = true;
     376                int line = 0;
     377                while (runNextLine) {
     378                        runNextLine = RunScriptLine(gRooms[0].fScripts[line]);
     379                        line++;
     380                }
     381                fMainWindow->SetMainText(gRooms[gCurrentRoom].fMessages[gCurrentScreen - 1]);
     382        }
     383
     384        void MessageReceived(BMessage* message) override
     385        {
     386                if (message->what < 256) {
     387                        gTrigger = message->what;
     388                        int line = 0;
     389                        bool runNextLine = true;
     390                        while (runNextLine) {
     391                                runNextLine = RunScriptLine(gRooms[0].fScripts[line]);
     392                                line++;
     393                        }
     394                        line = 0;
     395                        while (runNextLine) {
     396                                runNextLine = RunScriptLine(gRooms[gCurrentRoom].fScripts[line]);
     397                                line++;
     398                        }
     399                        fMainWindow->SetMainText(gRooms[gCurrentRoom].fMessages[gCurrentScreen - 1]);
     400                } else {
     401                        BApplication::MessageReceived(message);
     402                }
     403        }
     404
     405        bool RunScriptLine(const std::vector<uint8_t>& script)
     406        {
     407                int pc = 0;
     408                bool done = false;
     409                bool runNextLine = false;
     410
     411                while (!done) {
     412                        switch(script[pc++]) {
     413                                case 0x00:
     414                                {
     415                                        done = true;
     416                                        break;
     417                                }
     418                                case 0x09:
     419                                {
     420                                        uint8_t varAddress = script[pc++];
     421                                        uint16_t varValue = script[pc++];
     422                                        varValue = varValue | (script[pc++] << 8);
     423                                        if (gVar16[varAddress] == varValue) {
     424                                                continue;
     425                                        } else {
     426                                                done = true;
     427                                                runNextLine = true;
     428                                        }
     429                                }
     430                                case 0x0D:
     431                                {
     432                                        uint8_t eventToCheck = script[pc++];
     433                                        if (gTrigger == eventToCheck) {
     434                                                fprintf(stderr, "unknown param to 0D: %02x\n", script[pc++]);
     435                                                disassemble(script);
     436                                                continue;
     437                                        } else {
     438                                                done = true;
     439                                                runNextLine = true;
     440                                        }
     441                                        break;
     442                                }
     443                                case 0x0E:
     444                                {
     445                                        uint8_t eventToCheck = script[pc++];
     446                                        // FIXME is it really gTrigger here, or should it be something else?
     447                                        if (gTrigger == eventToCheck)
     448                                                continue;
     449                                        else {
     450                                                done = true;
     451                                                runNextLine = true;
     452                                        }
     453                                        break;
     454                                }
     455                                case 0x7F:
     456                                {
     457                                        // FIXME figure this out
     458                                        pc++;
     459                                        break;
     460                                }
     461                                case 0x87:
     462                                {
     463                                        uint8_t varAddress = script[pc++];
     464                                        uint16_t varValue = script[pc++];
     465                                        varValue = varValue | (script[pc++] << 8);
     466                                        gVar16[varAddress] = varValue;
     467                                        break;
     468                                }
     469                                case 0x8F:
     470                                {
     471                                        gCurrentRoom = script[pc++];
     472                                        break;
     473                                }
     474                                case 0x90:
     475                                {
     476                                        gCurrentScreen = script[pc++];
     477                                        break;
     478                                }
     479                                case 0x93:
     480                                {
     481                                        gCurrentScreen = script[pc++];
     482                                        break;
     483                                }
     484                                case 0x95:
     485                                {
     486                                        uint8_t varAddress = script[pc++];
     487                                        uint8_t varValue = script[pc++];
     488                                        gVar8[varAddress] = varValue;
     489                                        break;
     490                                }
     491                                default:
     492                                        fprintf(stderr, "ERROR: unknown opcode %02x at %04x\n", script[pc - 1], pc - 1);
     493                                        disassemble(script);
     494                                        done = true;
     495                                        break;
     496                        }
     497                }
     498
     499                return runNextLine;
     500        }
     501
     502private:
     503        GameWindow* fMainWindow;
     504};
    238505
    239506void usage()
     
    344611{
    345612        if (argc != 2) {
     613                // TODO open a filepanel instead to load a game
     614                // TODO or just look for the TITRES.DAT file
    346615                usage();
    347616                return 1;
     
    413682        ReadRoomsMessages(msgRoomsFile);
    414683
     684        // TODO how many variables are there?
     685        gVar8.resize(16);
     686        gVar16.resize(16);
     687
    415688        /*
    416689        int i = 0;
     
    435708        */
    436709
    437         BApplication app("application/x-vnd.PulkoMandy.Readingame");
    438 
    439         BWindow* window = new BWindow(BRect(40, 40, 300, 200), "Readingame", B_DOCUMENT_WINDOW,
    440                 B_AUTO_UPDATE_SIZE_LIMITS | B_QUIT_ON_WINDOW_CLOSE);
    441 
    442         window->Show();
     710        Readingame app;
    443711
    444712        app.Run();
Note: See TracChangeset for help on using the changeset viewer.