/*
 * This file is part of the planetblupi source code
 * Copyright (C) 1997, Daniel Roux & EPSITEC SA
 * Copyright (C) 2017-2019, Mathieu Schroeter
 * http://epsitec.ch; http://www.blupi.org; http://github.com/blupi-games
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://gnu.org/licenses
 */

#include <assert.h>
#include <ctime>
#include <set>
#include <stdio.h>
#include <stdlib.h>
#include <unordered_map>

#include "action.h"
#include "blupi.h"
#include "button.h"
#include "config.h"
#include "decor.h"
#include "def.h"
#include "event.h"
#include "gettext.h"
#include "menu.h"
#include "misc.h"
#include "movie.h"
#include "pixmap.h"
#include "platform.h"
#include "progress.h"
#include "sound.h"
#include "text.h"

#ifdef _WIN32
#define unlink _unlink
#define putenv _putenv
#else // _WIN32
#include <unistd.h>
#endif // !_WINE32

#define DEF_TIME_HELP 10000 // ~10 minutes
#define DEF_TIME_DEMO 1000  // ~1 minute
#define MAXDEMO 2000

typedef struct {
  // v1.0
  Sint16 majRev;
  Sint16 minRev;
  Sint16 reserve1[9];
  Sint16 exercice; // exercice en cours (0..n)
  Sint16 mission;  // mission en cours (0..n)
  Sint16 speed;
  Sint16 bMovie;
  Sint16 maxMission; // dernière mission effectuée (0..n)
  Sint16 scrollSpeed;
  Sint16 audioVolume;
  Sint16 midiVolume;
  Sint16 bAccessBuild;
  Sint16 prive;
  Sint16 skill;
  // v1.1
  Sint16 language;
  // v1.2
  Sint16 musicMidi;
  Sint16 fullScreen;
  Sint16 zoom;
  // v1.3
  Sint16 renderQuality;

  Sint16 reserve2[88];
} DescInfo;

// Toutes les premières lettres doivent
// être différentes !

static char cheat_code[9][20] = {
  "vision",      // 0
  "power",       // 1
  "lonesome",    // 2
  "allmissions", // 3
  "quick",       // 4
  "helpme",      // 5
  "invincible",  // 6
  "superblupi",  // 7
  "construire",  // 8 (CPOTUSVJSF)
};

/////////////////////////////////////////////////////////////////////////////

// clang-format off
static Phase table[] =
{
    {
        EV_PHASE_TESTCD,
        "init.png",
        "back-stars.png",
        CPixmap::Mode::FIX,
        false,
        {
            {
                0
            },
        },
    },

    {
        EV_PHASE_INTRO1,
        "intro1.png",
        "",
        CPixmap::Mode::FIX,
        false,
        {
            {
                0
            },
        },
    },

    {
        EV_PHASE_INIT,
        "init.png",
        "back-stars.png",
        CPixmap::Mode::FIX,
        false,
        {
            {
                EV_PHASE_DEMO,
                0, {1, 108},
                16, 424 - 60 - 42 * 4 - 18,
                { translate ("Demo") },
            },
            {
                EV_PHASE_SCHOOL,
                0, {1, 79},
                16, 424 - 60 - 42 * 3,
                { translate ("Training") },
            },
            {
                EV_PHASE_MISSION,
                0, {1, 80},
                16, 424 - 60 - 42 * 2,
                { translate ("Missions") },
            },
            {
                EV_PHASE_PRIVATE,
                0, {1, 49},
                16, 424 - 60 - 42 * 1,
                { translate ("Construction") },
            },
            {
                EV_PHASE_SETTINGS,
                0, {1, 47},
                16, 424 - 60 - 42 * 0,
                { translate ("Global settings") }
            },
            {
                EV_PHASE_BYE,
                0, {1, 36},
                16, 424,
                { translate ("Quit Planet Blupi") },
            },
            {
                0
            },
        },
    },

    {
        EV_PHASE_HISTORY0,
        "history0.png",
        "back-book.png",
        CPixmap::Mode::FIX_REVERSABLE,
        true,
        {
            {
                EV_PHASE_INIT,
                0, {1, 50},
                42 + 42 * 0, 433,
                { translate ("Previous page") },
            },
            {
                EV_PHASE_H1MOVIE,
                0, {1, 51},
                558 - 42 * 0, 433,
                { translate ("Next page") },
            },
            {
                0
            },
        },
    },

    {
        EV_PHASE_HISTORY1,
        "history1.png",
        "back-book.png",
        CPixmap::Mode::FIX_REVERSABLE,
        true,
        {
            {
                EV_PHASE_HISTORY0,
                0, {1, 50},
                42 + 42 * 0, 433,
                { translate ("Previous page") },
            },
            {
                EV_PHASE_H2MOVIE,
                0, {1, 51},
                558 - 42 * 0, 433,
                { translate ("Next page") },
            },
            {
                0
            },
        },
    },

    {
        EV_PHASE_INFO,
        "info%.3d.png",
        "back-book.png",
        CPixmap::Mode::FIX_REVERSABLE,
        false,
        {
            {
                EV_PREV,
                0, {1, 50},
                558 - 42 * 2, 433,
                { translate ("Previous game") },
            },
            {
                EV_PHASE_PLAYMOVIE,
                0, {1, 48},
                558 - 42 * 1, 433,
                { translate ("Play this game") },
            },
            {
                EV_NEXT,
                0, {1, 51},
                558 - 42 * 0, 433,
                { translate ("Next game") },
            },
            {
                EV_PHASE_READ,
                0, {1, 52},
                42 + 42 * 4, 433,
                { translate ("Open another game") },
            },
            {
                EV_PHASE_SETUP,
                0, {1, 47},
                42 + 42 * 7, 433,
                { translate ("Settings") },
            },
            {
                EV_PHASE_BUILD,
                0, {1, 49},
                42 + 42 * 8, 433,
                { translate ("Construct this game") },
            },
            {
                EV_PHASE_SKILL1,
                0, {1, 94},
                150, 230,
                { translate ("Skill level") },
            },
            {
                EV_PHASE_SKILL2,
                0, {1, 95},
                150, 230 + 42,
                { translate ("Skill level") },
            },
            {
                EV_PHASE_INIT,
                0, {1, 40},
                42 + 42 * 0, 433,
                { translate ("Finish") },
            },
            {
                0
            },
        },
    },

    {
        EV_PHASE_PLAY,
        "play.png",
        "",
        CPixmap::Mode::EXPAND_REVERSABLE,
        false,
        {
            {
                EV_PHASE_STOP,
                0, {1, 40},
                10 + 42 * 0, 422,
                {},
            },
            {
                EV_PHASE_SETUPp,
                0, {1, 47},
                10 + 42 * 1, 422,
                {},
            },
            {
                EV_PHASE_WRITEp,
                0, {1, 53},
                10 + 42 * 2, 422,
                {},
            },
            {
                0
            },
        },
    },

    {
        EV_PHASE_STOP,
        "stop%.3d.png",
        "back-book.png",
        CPixmap::Mode::FIX_REVERSABLE,
        false,
        {
            {
                EV_PHASE_PLAY,
                0, {1, 77},
                558 - 42 * 1, 433,
                { translate ("Continue this game") },
            },
            {
                EV_PHASE_READ,
                0, {1, 52},
                42 + 42 * 4, 433,
                { translate ("Open another game") },
            },
            {
                EV_PHASE_WRITE,
                0, {1, 53},
                42 + 42 * 5, 433,
                { translate ("Save this game") },
            },
            {
                EV_PHASE_SETUP,
                0, {1, 47},
                42 + 42 * 7, 433,
                { translate ("Settings") },
            },
            {
                EV_PHASE_INFO,
                0, {1, 78},
                42 + 42 * 0, 433,
                { translate ("Quit this game") },
            },
            {
                EV_PHASE_HELP,
                0, {1, 86},
                42 + 42 * 9, 433,
                { translate ("Help") },
            },
            {
                0
            },
        },
    },

    {
        EV_PHASE_HELP,
        "help.png",
        "back-book.png",
        CPixmap::Mode::FIX_REVERSABLE,
        true,
        {
            {
                EV_PHASE_PLAY,
                0, {1, 77},
                558 - 42 * 1, 433,
                { translate ("Continue this game") },
            },
            {
                EV_PHASE_READ,
                0, {1, 52},
                42 + 42 * 4, 433,
                { translate ("Open another game") },
            },
            {
                EV_PHASE_WRITE,
                0, {1, 53},
                42 + 42 * 5, 433,
                { translate ("Save this game") },
            },
            {
                EV_PHASE_SETUP,
                0, {1, 47},
                42 + 42 * 7, 433,
                { translate ("Settings") },
            },
            {
                EV_PHASE_STOP,
                0, {1, 50},
                42 + 42 * 0, 433,
                { translate ("Previous page") },
            },
            {
                0
            },
        },
    },

    {
        EV_PHASE_SETUP,
        "setup01.png",
        "back-setup.png",
        CPixmap::Mode::FIX,
        false,
        {
            {
                EV_BUTTON1,
                0, {1, 50},
                54, 330,
                { translate ("Slower") },
            },
            {
                EV_BUTTON2,
                0, {1, 51},
                54 + 40, 330,
                { translate ("Faster") },
            },
            {
                EV_BUTTON3,
                0, {1, 50},
                284, 330,
                { translate ("Reduce volume") },
            },
            {
                EV_BUTTON4,
                0, {1, 51},
                284 + 40, 330,
                { translate ("Increase volume") },
            },
            {
                EV_BUTTON5,
                0, {1, 50},
                399, 330,
                { translate ("Reduce volume") },
            },
            {
                EV_BUTTON6,
                0, {1, 51},
                399 + 40, 330,
                { translate ("Increase volume") },
            },
            {
                EV_BUTTON7,
                0, {1, 50},
                514, 330,
                { translate ("No video") },
            },
            {
                EV_BUTTON8,
                0, {1, 51},
                514 + 40, 330,
                { translate ("Show videos") },
            },
            {
                EV_BUTTON9,
                0, {1, 50},
                169, 330,
                { translate ("Slower") },
            },
            {
                EV_BUTTON10,
                0, {1, 51},
                169 + 40, 330,
                { translate ("Faster") },
            },
            {
                EV_SETUP_EXIT,
                0, {1, 40},
                11, 424,
                { translate ("Finish") },
            },
            {
                0
            },
        },
    },

    {
        EV_PHASE_SETUPp,
        "setup01.png",
        "back-setup.png",
        CPixmap::Mode::FIX,
        false,
        {
            {
                EV_BUTTON1,
                0, {1, 50},
                54, 330,
                { translate ("Slower") },
            },
            {
                EV_BUTTON2,
                0, {1, 51},
                54 + 40, 330,
                { translate ("Faster") },
            },
            {
                EV_BUTTON3,
                0, {1, 50},
                284, 330,
                { translate ("Reduce volume") },
            },
            {
                EV_BUTTON4,
                0, {1, 51},
                284 + 40, 330,
                { translate ("Increase volume") },
            },
            {
                EV_BUTTON5,
                0, {1, 50},
                399, 330,
                { translate ("Reduce volume") },
            },
            {
                EV_BUTTON6,
                0, {1, 51},
                399 + 40, 330,
                { translate ("Increase volume") },
            },
            {
                EV_BUTTON7,
                0, {1, 50},
                514, 330,
                { translate ("No video") },
            },
            {
                EV_BUTTON8,
                0, {1, 51},
                514 + 40, 330,
                { translate ("Show videos") },
            },
            {
                EV_BUTTON9,
                0, {1, 50},
                169, 330,
                { translate ("Slower") },
            },
            {
                EV_BUTTON10,
                0, {1, 51},
                169 + 40, 330,
                { translate ("Faster") },
            },
            {
                EV_PHASE_PLAY,
                0, {1, 77},
                11, 424,
                { translate ("Continue this game") },
            },
            {
                0
            },
        },
    },

    {
        EV_PHASE_READ,
        "read.png",
        "back-chest-r.png",
        CPixmap::Mode::FIX_REVERSABLE,
        false,
        {
            {
                EV_READ0,
                0, {0},
                420, 30 + 42 * 0,
                {},
            },
            {
                EV_READ1,
                0, {0},
                420, 30 + 42 * 1,
                {},
            },
            {
                EV_READ2,
                0, {0},
                420, 30 + 42 * 2,
                {},
            },
            {
                EV_READ3,
                0, {0},
                420, 30 + 42 * 3,
                {},
            },
            {
                EV_READ4,
                0, {0},
                420, 30 + 42 * 4,
                {},
            },
            {
                EV_READ5,
                0, {0},
                420, 30 + 42 * 5,
                {},
            },
            {
                EV_READ6,
                0, {0},
                420, 30 + 42 * 6,
                {},
            },
            {
                EV_READ7,
                0, {0},
                420, 30 + 42 * 7,
                {},
            },
            {
                EV_READ8,
                0, {0},
                420, 30 + 42 * 8,
                {},
            },
            {
                EV_READ9,
                0, {0},
                420, 30 + 42 * 9,
                {},
            },
            {
                EV_READ_EXIT,
                0, {1, 40},
                16, 424,
                { translate ("Finish") },
            },
            {
                0
            },
        },
    },

    {
        EV_PHASE_WRITE,
        "write.png",
        "back-chest-w.png",
        CPixmap::Mode::FIX_REVERSABLE,
        false,
        {
            {
                EV_WRITE0,
                0, {0},
                420, 30 + 42 * 0,
                {},
            },
            {
                EV_WRITE1,
                0, {0},
                420, 30 + 42 * 1,
                {},
            },
            {
                EV_WRITE2,
                0, {0},
                420, 30 + 42 * 2,
                {},
            },
            {
                EV_WRITE3,
                0, {0},
                420, 30 + 42 * 3,
                {},
            },
            {
                EV_WRITE4,
                0, {0},
                420, 30 + 42 * 4,
                {},
            },
            {
                EV_WRITE5,
                0, {0},
                420, 30 + 42 * 5,
                {},
            },
            {
                EV_WRITE6,
                0, {0},
                420, 30 + 42 * 6,
                {},
            },
            {
                EV_WRITE7,
                0, {0},
                420, 30 + 42 * 7,
                {},
            },
            {
                EV_WRITE8,
                0, {0},
                420, 30 + 42 * 8,
                {},
            },
            {
                EV_WRITE9,
                0, {0},
                420, 30 + 42 * 9,
                {},
            },
            {
                EV_PHASE_STOP,
                0, {1, 40},
                16, 424,
                { translate ("Finish") },
            },
            {
                0
            },
        },
    },

    {
        EV_PHASE_WRITEp,
        "write.png",
        "back-chest-w.png",
        CPixmap::Mode::FIX_REVERSABLE,
        false,
        {
            {
                EV_WRITE0,
                0, {0},
                420, 30 + 42 * 0,
                {},
            },
            {
                EV_WRITE1,
                0, {0},
                420, 30 + 42 * 1,
                {},
            },
            {
                EV_WRITE2,
                0, {0},
                420, 30 + 42 * 2,
                {},
            },
            {
                EV_WRITE3,
                0, {0},
                420, 30 + 42 * 3,
                {},
            },
            {
                EV_WRITE4,
                0, {0},
                420, 30 + 42 * 4,
                {},
            },
            {
                EV_WRITE5,
                0, {0},
                420, 30 + 42 * 5,
                {},
            },
            {
                EV_WRITE6,
                0, {0},
                420, 30 + 42 * 6,
                {},
            },
            {
                EV_WRITE7,
                0, {0},
                420, 30 + 42 * 7,
                {},
            },
            {
                EV_WRITE8,
                0, {0},
                420, 30 + 42 * 8,
                {},
            },
            {
                EV_WRITE9,
                0, {0},
                420, 30 + 42 * 9,
                {},
            },
            {
                EV_PHASE_PLAY,
                0, {1, 77},
                16, 424,
                { translate ("Continue this game") },
            },
            {
                0
            },
        },
    },

    {
        EV_PHASE_LOST,
        "lost.png",
        "back-lost.png",
        CPixmap::Mode::FIX,
        true,
        {
            {
                EV_PHASE_INFO,
                0, {1, 50},
                9, 431,
                { translate ("Restart this game") },
            },
            {
                0
            },
        },
    },

    {
        EV_PHASE_WIN,
        "win.png",
        "back-win.png",
        CPixmap::Mode::FIX,
        true,
        {
            {
                EV_NEXT,
                0, {1, 51},
                9, 431,
                { translate ("Next game") },
            },
            {
                0
            },
        },
    },

    {
        EV_PHASE_LASTWIN,
        "last%.3d.png",
        "",
        CPixmap::Mode::FIX,
        true,
        {
            {
                EV_PHASE_INIT,
                0, {1, 51},
                9, 431,
                { translate ("Next game") },
            },
            {
                0
            },
        },
    },

    {
        EV_PHASE_BUILD,
        "build.png",
        "",
        CPixmap::Mode::EXPAND_REVERSABLE,
        true,
        {
            {
                EV_DECOR1,      // pose des sols
                0, {6, 0, 1, 2, 3, 4, 25},
                11 + 42 * 2, 190 + 42 * 0,
                {
                    translate ("Normal ground"),
                    translate ("Inflammable ground"),
                    translate ("Sterile ground"),
                    translate ("Water"),
                    translate ("Special pavings"),
                    translate ("Incubator or teleporter")
                },
            },
            {
                EV_DECOR2,      // pose des plantes
                0, {4, 6, 7, 8, 11},
                11 + 42 * 2, 190 + 42 * 1,
                {
                    translate ("Delete item"),
                    translate ("Decorative plants"),
                    translate ("Tree"),
                    translate ("Flowers")
                },
            },
            {
                EV_DECOR3,      // pose des batiments
                0, {11, 18, 81, 33, 61, 82, 93, 20, 21, 22, 57, 58},
                11 + 42 * 2, 190 + 42 * 2,
                {
                    translate ("Delete item"),
                    translate ("Buildings"),
                    translate ("Protection tower"),
                    translate ("Mine"),
                    translate ("Enemy buildings"),
                    translate ("Enemy barrier"),
                    translate ("Wall or palisade"),
                    translate ("Rocks"),
                    translate ("Items"),
                    translate ("Weapons"),
                    translate ("Transport")
                },
            },
            {
                EV_DECOR4,      // pose des blupi
                0, {10, 12, 13, 14, 85, 15, 16, 17, 38, 75, 56},
                11 + 42 * 2, 190 + 42 * 3,
                {
                    translate ("Delete figure"),
                    translate ("Tired Blupi"),
                    translate ("Blupi"),
                    translate ("Helper robot"),
                    translate ("Spider"),
                    translate ("Virus"),
                    translate ("Bulldozer"),
                    translate ("Bouncing bomb"),
                    translate ("Electrocutor"),
                    translate ("Master robot")
                },
            },
            {
                EV_DECOR5,      // pose les catastrophes
                0, {2, 36, 37},
                11 + 42 * 2, 190 + 42 * 4,
                {
                    translate ("Delete fire"),
                    translate ("Starting fire")
                },
            },
            {
                EV_PHASE_REGION,
                0, {1, 5},
                11 + 42 * 0, 190 + 42 * 1,
                { translate ("Scenery choice") },
            },
            {
                EV_PHASE_MUSIC,
                0, {1, 44},
                11 + 42 * 0, 190 + 42 * 2,
                { translate ("Music choice") },
            },
            {
                EV_PHASE_BUTTON,
                0, {1, 46},
                11 + 42 * 0, 190 + 42 * 3,
                { translate ("Available buttons") },
            },
            {
                EV_PHASE_TERM,
                0, {1, 45},
                11 + 42 * 0, 190 + 42 * 4,
                { translate ("Ending conditions") },
            },
            {
                EV_PHASE_INFO,
                0, {1, 40},
                11 + 42 * 0, 424,
                { translate ("Quit construction") },
            },
            {
                EV_PHASE_UNDO,
                0, {1, 87},
                11 + 42 * 2, 424,
                { translate ("Cancel last operation") },
            },
            {
                0
            },
        },
    },

    {
        EV_PHASE_BUTTON,
        "button.png",
        "back-build.png",
        CPixmap::Mode::FIX_REVERSABLE,
        true,
        {
            {
                EV_BUTTON1,     // stop
                0, {1, 40},
                170 + 42 * 0, 30 + 52 * 0,
                { translate ("Stop") },
            },
            {
                EV_BUTTON0,     // go
                0, {1, 24},
                170 + 42 * 1, 30 + 52 * 0,
                { translate ("Go") },
            },
            {
                EV_BUTTON3,     // carry
                0, {1, 30},
                170 + 42 * 3, 30 + 52 * 0,
                { translate ("Take") },
            },
            {
                EV_BUTTON4,     // depose
                0, {1, 31},
                170 + 42 * 4, 30 + 52 * 0,
                { translate ("Drop") },
            },
            {
                EV_BUTTON32,    // répète
                0, {1, 100},
                170 + 42 * 6, 30 + 52 * 0,
                { translate ("Repeat") },
            },

            {
                EV_BUTTON5,     // abat
                0, {1, 22},
                170 + 42 * 0, 30 + 52 * 1,
                { translate ("Cut down a tree") },
            },
            {
                EV_BUTTON16,    // abat n
                0, {1, 42},
                170 + 42 * 1, 30 + 52 * 1,
                { translate ("Cut down trees") },
            },
            {
                EV_BUTTON6,     // roc
                0, {1, 27},
                170 + 42 * 3, 30 + 52 * 1,
                { translate ("Carve a rock") },
            },
            {
                EV_BUTTON17,    // roc n
                0, {1, 43},
                170 + 42 * 4, 30 + 52 * 1,
                { translate ("Carve rocks") },
            },
            {
                EV_BUTTON22,    // fleurs
                0, {1, 54},
                170 + 42 * 6, 30 + 52 * 1,
                { translate ("Make bunch of flowers") },
            },
            {
                EV_BUTTON23,    // fleurs n
                0, {1, 55},
                170 + 42 * 7, 30 + 52 * 1,
                { translate ("Make bunches of flowers") },
            },

            {
                EV_BUTTON9,     // build2 (couveuse)
                0, {1, 25},
                170 + 42 * 0, 30 + 52 * 2,
                { translate ("Incubator") },
            },
            {
                EV_BUTTON15,    // palis
                0, {1, 26},
                170 + 42 * 1, 30 + 52 * 2,
                { translate ("Palisade") },
            },
            {
                EV_BUTTON18,    // pont
                0, {1, 23},
                170 + 42 * 2, 30 + 52 * 2,
                { translate ("Bridge") },
            },
            {
                EV_BUTTON25,    // bateau
                0, {1, 58},
                170 + 42 * 3, 30 + 52 * 2,
                { translate ("Boat") },
            },
            {
                EV_BUTTON13,    // build6 (téléporteur)
                0, {1, 101},
                170 + 42 * 4, 30 + 52 * 2,
                { translate ("Teleporter") },
            },
            {
                EV_BUTTON14,    // mur
                0, {1, 20},
                170 + 42 * 6, 30 + 52 * 2,
                { translate ("Wall") },
            },
            {
                EV_BUTTON19,    // tour
                0, {1, 33},
                170 + 42 * 7, 30 + 52 * 2,
                { translate ("Protection tower") },
            },

            {
                EV_BUTTON8,     // build1 (cabane)
                0, {1, 19},
                170 + 42 * 0, 30 + 52 * 3,
                { translate ("Garden shed") },
            },
            {
                EV_BUTTON7,     // cultive
                0, {1, 28},
                170 + 42 * 1, 30 + 52 * 3,
                { translate ("Grow tomatoes") },
            },
            {
                EV_BUTTON2,     // mange
                0, {1, 32},
                170 + 42 * 2, 30 + 52 * 3,
                { translate ("Eat") },
            },

            {
                EV_BUTTON10,    // build3 (laboratoire)
                0, {1, 35},
                170 + 42 * 0, 30 + 52 * 4,
                { translate ("Laboratory") },
            },
            {
                EV_BUTTON21,    // laboratoire
                0, {1, 39},
                170 + 42 * 1, 30 + 52 * 4,
                { translate ("Transform") },
            },
            {
                EV_BUTTON20,    // boit
                0, {1, 34},
                170 + 42 * 2, 30 + 52 * 4,
                { translate ("Drink") },
            },
            {
                EV_BUTTON24,    // dynamite
                0, {1, 41},
                170 + 42 * 3, 30 + 52 * 4,
                { translate ("Blow up") },
            },

            {
                EV_BUTTON27,    // drapeau
                0, {1, 64},
                170 + 42 * 0, 30 + 52 * 5,
                { translate ("Prospect for iron") },
            },
            {
                EV_BUTTON11,    // build4 (mine)
                0, {1, 61},
                170 + 42 * 1, 30 + 52 * 5,
                { translate ("Mine") },
            },
            {
                EV_BUTTON28,    // extrait
                0, {1, 62},
                170 + 42 * 2, 30 + 52 * 5,
                { translate ("Extract iron") },
            },
            {
                EV_BUTTON12,    // build5 (usine)
                0, {1, 59},
                170 + 42 * 4, 30 + 52 * 5,
                { translate ("Workshop") },
            },
            {
                EV_BUTTON29,    // fabrique jeep
                0, {1, 65},
                170 + 42 * 5, 30 + 52 * 5,
                { translate ("Make a Jeep") },
            },
            {
                EV_BUTTON30,    // fabrique mine
                0, {1, 63},
                170 + 42 * 6, 30 + 52 * 5,
                { translate ("Make a time bomb") },
            },
            {
                EV_BUTTON34,    // fabrique armure
                0, {1, 106},
                170 + 42 * 7, 30 + 52 * 5,
                { translate ("Make armour") },
            },
            {
                EV_BUTTON31,    // fabrique disciple
                0, {1, 83},
                170 + 42 * 8, 30 + 52 * 5,
                { translate ("Make a helper robot") },
            },
            {
                EV_PHASE_BUILD,
                0, {1, 50},
                11, 424,
                { translate ("Finish") },
            },
            {
                0
            },
        },
    },

    {
        EV_PHASE_TERM,
        "term.png",
        "back-build.png",
        CPixmap::Mode::FIX_REVERSABLE,
        true,
        {
            {
                EV_BUTTON8,     // home blupi
                0, {1, 81},
                170 + 42 * 0, 30 + 42 * 0,
                { translate ("Blupi in house") },
            },
            {
                EV_BUTTON9,     // kill robots
                0, {1, 57},
                170 + 42 * 1, 30 + 42 * 0,
                { translate ("No more enemies") },
            },
            {
                EV_BUTTON3,     // stop fire
                0, {1, 37},
                170 + 42 * 2, 30 + 42 * 0,
                { translate ("Fire out") },
            },
            {
                EV_BUTTON1,     // hach blupi
                0, {1, 14},
                170 + 42 * 0, 30 + 42 * 2,
                { translate ("Blupi on striped paving stones") },
            },
            {
                EV_BUTTON2,     // hach planche
                0, {1, 22},
                170 + 42 * 1, 30 + 42 * 2,
                { translate ("Planks on striped paving stones") },
            },
            {
                EV_BUTTON10,    // hach tomate
                0, {1, 28},
                170 + 42 * 2, 30 + 42 * 2,
                { translate ("Tomatoes on striped paving stones") },
            },
            {
                EV_BUTTON11,    // hach métal
                0, {1, 84},
                170 + 42 * 3, 30 + 42 * 2,
                { translate ("Platinium on striped paving stones") },
            },
            {
                EV_BUTTON12,    // hach robot
                0, {1, 94},
                170 + 42 * 4, 30 + 42 * 2,
                { translate ("Robot on striped paving stones") },
            },
            {
                EV_BUTTON4,     // - min blupi
                0, {1, 50},
                170 + 42 * 0, 30 + 42 * 4,
                { "(-)" },
            },
            {
                EV_BUTTON5,     // + min blupi
                0, {1, 51},
                170 + 42 * 1, 30 + 42 * 4,
                { "(+)" },
            },
            {
                EV_BUTTON6,     // - max blupi
                0, {1, 50},
                170 + 42 * 0, 30 + 42 * 5,
                { "(-)" },
            },
            {
                EV_BUTTON7,     // + max blupi
                0, {1, 51},
                170 + 42 * 1, 30 + 42 * 5,
                { "(+)" },
            },
            {
                EV_PHASE_BUILD,
                0, {1, 50},
                11, 424,
                { translate ("Finish") },
            },
            {
                0
            },
        },
    },

    {
        EV_PHASE_MUSIC,
        "music.png",
        "back-build.png",
        CPixmap::Mode::FIX_REVERSABLE,
        true,
        {
            {
                EV_BUTTON1,
                0, {1, 40},
                11, 101,
                { translate ("No music") },
            },
            {
                EV_BUTTON2,
                0, {1, 44},
                170 + 42 * 0, 30 + 42 * 0,
                { translate ("Music number 1") },
            },
            {
                EV_BUTTON3,
                0, {1, 44},
                170 + 42 * 0, 30 + 42 * 1,
                { translate ("Music number 2") },
            },
            {
                EV_BUTTON4,
                0, {1, 44},
                170 + 42 * 0, 30 + 42 * 2,
                { translate ("Music number 3") },
            },
            {
                EV_BUTTON5,
                0, {1, 44},
                170 + 42 * 0, 30 + 42 * 3,
                { translate ("Music number 4") },
            },
            {
                EV_BUTTON6,
                0, {1, 44},
                170 + 42 * 0, 30 + 42 * 4,
                { translate ("Music number 5") },
            },
            {
                EV_BUTTON7,
                0, {1, 44},
                170 + 42 * 0, 30 + 42 * 5,
                { translate ("Music number 6") },
            },
            {
                EV_BUTTON8,
                0, {1, 44},
                170 + 42 * 0, 30 + 42 * 6,
                { translate ("Music number 7") },
            },
            {
                EV_BUTTON9,
                0, {1, 44},
                170 + 42 * 0, 30 + 42 * 7,
                { translate ("Music number 8") },
            },
            {
                EV_BUTTON10,
                0, {1, 44},
                170 + 42 * 0, 30 + 42 * 8,
                { translate ("Music number 9") },
            },
            {
                EV_BUTTON11,
                0, {1, 44},
                170 + 42 * 0, 30 + 42 * 9,
                { translate ("Music number 10") },
            },
            {
                EV_PHASE_BUILD,
                0, {1, 50},
                11, 424,
                { translate ("Finish") },
            },
            {
                0
            },
        },
    },

    {
        EV_PHASE_REGION,
        "region.png",
        "back-build.png",
        CPixmap::Mode::FIX_REVERSABLE,
        true,
        {
            {
                EV_BUTTON1,     // normal
                0, {0},
                220, 60,
                { translate ("Prairie") },
            },
            {
                EV_BUTTON4,     // sapins
                0, {0},
                220, 170,
                { translate ("Forest") },
            },
            {
                EV_BUTTON2,     // palmiers
                0, {0},
                220, 280,
                { translate ("Desert") },
            },
            {
                EV_BUTTON3,     // hivers
                0, {0},
                220, 390,
                { translate ("Forest under snow") },
            },
            {
                EV_PHASE_BUILD,
                0, {1, 50},
                11, 424,
                { translate ("Finish") },
            },
            {
                0
            },
        },
    },

    {
        EV_PHASE_PLAYMOVIE,
        "movie.png",
        "",
        CPixmap::Mode::FIX,
        false,
        {
            {
                0
            },
        },
    },

    {
        EV_PHASE_WINMOVIE,
        "movie.png",
        "",
        CPixmap::Mode::FIX,
        false,
        {
            {
                0
            },
        },
    },

    {
        EV_PHASE_H0MOVIE,
        "movie.png",
        "",
        CPixmap::Mode::FIX,
        false,
        {
            {
                0
            },
        },
    },

    {
        EV_PHASE_H1MOVIE,
        "movie.png",
        "",
        CPixmap::Mode::FIX,
        false,
        {
            {
                0
            },
        },
    },

    {
        EV_PHASE_H2MOVIE,
        "movie.png",
        "",
        CPixmap::Mode::FIX,
        false,
        {
            {
                0
            },
        },
    },

    {
        EV_PHASE_BYE,
        "bye.png",
        "back-bye.png",
        CPixmap::Mode::FIX,
        false,
        {
            {
                0
            },
        },
    },

    {
        EV_PHASE_INSERT,
        "insert.png",
        "",
        CPixmap::Mode::FIX,
        false,
        {
            {
                EV_PHASE_INIT,
                0, {1, 40},
                16, 424,
                { translate ("Quit Planet Blupi") },
            },
            {
                0
            },
        },
    },

    {
        EV_PHASE_SETTINGS,
        "setup00.png",
        "back-setup.png",
        CPixmap::Mode::FIX,
        false,
        {
            {
                EV_BUTTON1,
                0, {1, 50},
                54, 330,
                { translate ("Previous language") },
            },
            {
                EV_BUTTON2,
                0, {1, 51},
                54 + 40, 330,
                { translate ("Next language") },
            },
            {
                EV_BUTTON3,
                0, {1, 50},
                169, 330,
                { translate ("Fullscreen") },
            },
            {
                EV_BUTTON4,
                0, {1, 51},
                169 + 40, 330,
                { translate ("Windowed") },
            },
            {
                EV_BUTTON5,
                0, {1, 50},
                284, 330,
                { "" /* dynamic */ },
            },
            {
                EV_BUTTON6,
                0, {1, 51},
                284 + 40, 330,
                { "" /* dynamic */ },
            },
            {
                EV_BUTTON7,
                0, {1, 50},
                399, 330,
                { translate ("Use Ogg music") },
            },
            {
                EV_BUTTON8,
                0, {1, 51},
                399 + 40, 330,
                { translate ("Use Midi music (original)") },
            },
            {
                EV_BUTTON9,
                0, {1, 50},
                514, 330,
                { translate ("Disable anti-aliasing") },
            },
            {
                EV_BUTTON10,
                0, {1, 51},
                514 + 40, 330,
                { translate ("Enable anti-aliasing") },
            },
            {
                EV_PHASE_INIT,
                0, {1, 40},
                42 + 42 * 0, 433,
                { translate ("Finish") },
            },
            {
                0
            },
        },
    },

    {
        0
    }
};
// clang-format on

/////////////////////////////////////////////////////////////////////////////

// Constructeur.

CEvent::CEvent ()
{
  Sint32 i;

  m_exercice        = 0;
  m_mission         = 0;
  m_private         = 0;
  m_maxMission      = 0;
  m_phase           = 0;
  m_index           = -1;
  m_bSchool         = false;
  m_bPrivate        = false;
  m_bAccessBuild    = false;
  m_bRunMovie       = false;
  m_bBuildModify    = false;
  m_bMouseDown      = false;
  m_oldMousePos.x   = 0;
  m_oldMousePos.y   = 0;
  m_mouseSprite     = SPRITE_ARROW;
  m_bFillMouse      = false;
  m_bWaitMouse      = false;
  m_bHideMouse      = false;
  m_rankCheat       = -1;
  m_posCheat        = 0;
  m_speed           = 1;
  m_bMovie          = true;
  m_bSpeed          = false;
  m_bHelp           = false;
  m_bAllMissions    = false;
  m_scrollSpeed     = 1;
  m_scrollSpeedPrev = -1;
  m_bPause          = false;
  m_bShift          = false;
  m_shiftPhase      = 0;
  m_movieToStart[0] = 0;
  m_bInfoHelp       = false;
  m_bDemoRec        = false;
  m_bDemoPlay       = false;
  m_pDemoBuffer     = nullptr;
  m_bStartRecording = false;
  m_demoTime        = 0;
  m_keymod          = 0;
  m_posHelpButton   = {-1, -1};
  m_pPixmap         = nullptr;
  m_pDecor          = nullptr;
  m_pSound          = nullptr;
  m_pMovie          = nullptr;
  m_bMenu           = false;
  m_bHili           = false;
  m_demoIndex       = 0;
  m_demoEnd         = 0;
  m_bHiliInfoButton = false;
  m_bHiliHelpButton = false;

  memset (m_textToolTips, 0, sizeof (m_textToolTips));
  memset (m_libelle, 0, sizeof (m_libelle));

  for (i = 0; i < MAXBUTTON; i++)
    m_lastFloor[i] = 0;

  for (i = 0; i < MAXBUTTON; i++)
    m_lastObject[i] = 0;

  for (i = 0; i < MAXBUTTON; i++)
    m_lastHome[i] = 0;

  // TODO: should be dynamic by looking in the locale directory
  m_Languages.push_back (Language::en);
  m_Languages.push_back (Language::en_US);
  m_Languages.push_back (Language::fr);
  m_Languages.push_back (Language::de);
  m_Languages.push_back (Language::it);
  m_Languages.push_back (Language::pl);
  m_Languages.push_back (Language::tr);
  m_Languages.push_back (Language::pt);
  m_Languages.push_back (Language::he);

  this->m_LangStart = GetLocale ();

  if (this->m_LangStart == "en_US")
    m_Lang = m_Languages.begin () + 1;
  else if (this->m_LangStart == "fr")
    m_Lang = m_Languages.begin () + 2;
  else if (this->m_LangStart == "de")
    m_Lang = m_Languages.begin () + 3;
  else if (this->m_LangStart == "it")
    m_Lang = m_Languages.begin () + 4;
  else if (this->m_LangStart == "pl")
    m_Lang = m_Languages.begin () + 5;
  else if (this->m_LangStart == "tr")
    m_Lang = m_Languages.begin () + 6;
  else if (this->m_LangStart == "pt")
    m_Lang = m_Languages.begin () + 7;
  else if (this->m_LangStart == "he")
    m_Lang = m_Languages.begin () + 8;
  else
    m_Lang = m_Languages.begin ();

  m_updateBlinking = 0;

  this->shiftDirection = 0;
  this->statDisabled   = false;
}

// Destructeur.

CEvent::~CEvent ()
{
  WriteInfo (); // lit le fichier "info.blp"
}

bool
CEvent::IsDemoPlaying ()
{
  return this->m_bDemoPlay;
}

// Retourne la position de la souris.

Point
CEvent::GetMousePos ()
{
  Point  pos;
  Sint32 x, y;

  SDL_GetMouseState (&x, &y);
  pos.x = x;
  pos.y = y;

  return pos;
}

// Initialise le mode full screen ou non.

void
CEvent::SetFullScreen (bool bFullScreen, double prevScale)
{
  g_bFullScreen = bFullScreen;

  if (Platform::getType () == Platform::Type::JS)
    return;

  int x, y;
  SDL_GetMouseState (&x, &y);
  this->m_pPixmap->FromDisplayToGame (x, y, prevScale);

  int displayIndex = SDL_GetWindowDisplayIndex (g_window);
#ifdef _WIN32
  if (g_zoom == 2)
    displayIndex = 0;
#endif /* _WIN32 */

  SDL_RenderClear (g_renderer);
  SDL_RenderPresent (g_renderer);

  if (g_bFullScreen && g_zoom == 2)
  {
    int displays = SDL_GetNumVideoDisplays ();

    std::vector<SDL_Rect> displayBounds;
    for (int i = 0; i < displays; i++)
    {
      displayBounds.push_back (SDL_Rect ());
      SDL_GetDisplayBounds (i, &displayBounds.back ());
    }

    /* It seems that the fullscreen switching works better when the window
     * is at the top left corner of the current display.
     */
    SDL_SetWindowPosition (
      g_window, displayBounds[displayIndex].x, displayBounds[displayIndex].y);
  }

  SDL_SetWindowFullscreen (
    g_window, bFullScreen ? (g_zoom == 1 ? SDL_WINDOW_FULLSCREEN_DESKTOP
                                         : SDL_WINDOW_FULLSCREEN)
                          : 0);
  SDL_SetWindowBordered (g_window, bFullScreen ? SDL_FALSE : SDL_TRUE);
  SDL_SetWindowGrab (g_window, bFullScreen ? SDL_TRUE : SDL_FALSE);

  m_pPixmap->LoadCursors ();

  /* Force this update before otherwise the coordinates retrieved with
   * the Warp SDL function are corresponding to the previous size.
   */
  CEvent::PushUserEvent (EV_UPDATE);

  auto coord = new SDL_Point; // Released by the event handler.
  coord->x   = x;
  coord->y   = y;
  CEvent::PushUserEvent (EV_WARPMOUSE, coord);
}

/**
 * \brief Change the size of the window.
 *
 * We use an integer scale to be sure that the pixels are always well formed.
 *
 * \param[in] newScale - The new scale.
 */
void
CEvent::SetWindowSize (Uint8 newScale)
{
  auto scale = g_zoom;
  g_zoom     = newScale;
  switch (newScale)
  {
  case 1:
  case 2:
    SetWindowSize (scale, g_zoom);
    break;

  default:
    return;
  }
}

/**
 * \brief Change the size of the window.
 *
 * We use an integer scale to be sure that the pixels are always well formed.
 *
 * \param[in] prevScale - The current scale.
 * \param[in] newScale - The new scale.
 */
void
CEvent::SetWindowSize (double prevScale, double newScale)
{
  int x, y;
  SDL_GetMouseState (&x, &y);
  this->m_pPixmap->FromDisplayToGame (x, y, prevScale);

  if (g_bFullScreen && newScale == 2)
    newScale = 1;

  SDL_SetWindowSize (g_window, LXIMAGE () * newScale, LYIMAGE () * newScale);
  SDL_RenderClear (g_renderer);
  SDL_RenderPresent (g_renderer);

  int displayIndex = SDL_GetWindowDisplayIndex (g_window);
  SDL_SetWindowPosition (
    g_window, SDL_WINDOWPOS_CENTERED_DISPLAY (displayIndex),
    SDL_WINDOWPOS_CENTERED_DISPLAY (displayIndex));
  SDL_Delay (100);

  m_pPixmap->LoadCursors ();

  if (prevScale == newScale)
    return;

  /* Force this update before otherwise the coordinates retrieved with
   * the Warp SDL function are corresponding to the previous size.
   */
  CEvent::PushUserEvent (EV_UPDATE);

  auto coord = new SDL_Point; // Released by the event handler.
  coord->x   = x;
  coord->y   = y;
  CEvent::PushUserEvent (EV_WARPMOUSE, coord);
}

// Crée le gestionnaire d'événements.

void
CEvent::Create (
  CPixmap * pPixmap, CDecor * pDecor, CSound * pSound, CMovie * pMovie)
{
  Point pos;

  m_pPixmap = pPixmap;
  m_pDecor  = pDecor;
  m_pSound  = pSound;
  m_pMovie  = pMovie;

  ReadInfo (); // lit le fichier "info.blp"

  pos.x = 10;
  pos.y = 158;
  m_jauges[0].Create (m_pPixmap, m_pSound, pos, 1);

  pos.y += DIMJAUGEY + 2;
  m_jauges[1].Create (m_pPixmap, m_pSound, pos, 3);
}

// Retourne l'index d'un bouton.

Sint32
CEvent::GetButtonIndex (Sint32 button)
{
  int i = 0;

  if (m_index < 0)
    return -1;

  while (table[m_index].buttons[i].message != 0)
  {
    if ((Uint32) button == table[m_index].buttons[i].message)
      return i;
    i++;
  }

  return -1;
}

Sint32
CEvent::GetState (Sint32 button)
{
  Sint32 index;

  index = GetButtonIndex (button);
  if (index < 0)
    return 0;

  return m_buttons[index].GetState ();
}

void
CEvent::SetState (Sint32 button, Sint32 state)
{
  Sint32 index;

  index = GetButtonIndex (button);
  if (index < 0)
    return;

  m_buttons[index].SetState (state);
}

bool
CEvent::GetEnable (Sint32 button)
{
  Sint32 index;

  index = GetButtonIndex (button);
  if (index < 0)
    return 0;

  return m_buttons[index].GetEnable ();
}

void
CEvent::SetEnable (Sint32 button, bool bEnable)
{
  Sint32 index;

  index = GetButtonIndex (button);
  if (index < 0)
    return;

  m_buttons[index].SetEnable (bEnable);
}

bool
CEvent::GetHide (Sint32 button)
{
  Sint32 index;

  index = GetButtonIndex (button);
  if (index < 0)
    return 0;

  return m_buttons[index].GetHide ();
}

void
CEvent::SetHide (Sint32 button, bool bHide)
{
  Sint32 index;

  index = GetButtonIndex (button);
  if (index < 0)
    return;

  m_buttons[index].SetHide (bHide);
}

Sint32
CEvent::GetMenu (Sint32 button)
{
  Sint32 index;

  index = GetButtonIndex (button);
  if (index < 0)
    return 0;

  return m_buttons[index].GetMenu ();
}

void
CEvent::SetMenu (Sint32 button, Sint32 menu)
{
  Sint32 index;

  index = GetButtonIndex (button);
  if (index < 0)
    return;

  m_buttons[index].SetMenu (menu);
}

// Crée tous les boutons nécessaires à la phase en cours.

bool
CEvent::CreateButtons (Sint32 phase)
{
  Sint32 i = 0, message;
  Point  pos;

  if (m_index < 0)
    return false;

  while (table[m_index].buttons[i].message != 0)
  {
    pos.x   = table[m_index].buttons[i].x;
    pos.y   = table[m_index].buttons[i].y;
    message = table[m_index].buttons[i].message;

    auto isRightReading =
      ((message == EV_PHASE_INIT || message == EV_PHASE_PLAY ||
        message == EV_SETUP_EXIT) ||
       (phase != EV_PHASE_SETTINGS && phase != EV_PHASE_SETUP &&
        phase != EV_PHASE_SETUPp)) &&
      IsRightReading ();

    if (isRightReading)
      pos.x = LXIMAGE () - pos.x - DIMBUTTONX;

    if (
      phase != EV_PHASE_PLAY && phase != EV_PHASE_BUILD &&
      phase != EV_PHASE_INIT)
      pos.x = isRightReading ? pos.x - LXOFFSET () : pos.x + LXOFFSET ();

    if (m_bPrivate)
    {
      if (message == EV_PHASE_SKILL1)
      {
        pos.x = 117 + LXOFFSET ();
        pos.y = 115;
        if (isRightReading)
          pos.x = LXIMAGE () - pos.x - DIMBUTTONX;
      }
      if (message == EV_PHASE_SKILL2)
      {
        pos.x = 117 + LXOFFSET ();
        pos.y = 115 + 42;
        if (isRightReading)
          pos.x = LXIMAGE () - pos.x - DIMBUTTONX;
      }
    }

    m_buttons[i].Create (
      m_pPixmap, m_pSound, pos, table[m_index].buttons[i].type,
      table[m_index].buttons[i].iconMenu + 1,
      table[m_index].buttons[i].iconMenu[0], table[m_index].buttons[i].toolTips,
      m_pDecor->GetRegion (), message, isRightReading);
    i++;
  }

  return true;
}

// Ajoute un cheat-code dans un buffer.

void
AddCheatCode (char * pDst, char * pSrc)
{
  Sint32 i;
  size_t j;

  if (pDst[0] != 0)
    strcat (pDst, " / ");

  i = 0;
  j = strlen (pDst);
  while (pSrc[i] != 0)
    pDst[j++] = pSrc[i++];
  pDst[j] = 0;
}

void
CEvent::SetUpdateVersion (const std::string & version)
{
  this->m_updateVersion = version;
}

// Dessine un texte multi-lignes centré.

void
CEvent::DrawTextCenter (const char * text, Sint32 x, Sint32 y, Sint32 font)
{
  Point pos;
  pos.x = x;
  pos.y = y;
  ::DrawTextCenter (m_pPixmap, pos, text, font);
}

// Dessine tous les boutons de la phase en cours.

bool
CEvent::DrawButtons ()
{
  Sint32 i;
  Sint32 levels[2];
  Sint32 types[2];
  Sint32 world, time, lg, button, volume, pente, icon;
  char   res[100];
  char   text[100];
  Point  pos;
  Rect   rect;
  bool   bEnable;

  if (
    (m_phase == EV_PHASE_PLAY) ||
    (m_phase != EV_PHASE_PLAY && m_phase != EV_PHASE_INSERT &&
     m_phase != EV_PHASE_INTRO1 && m_phase != EV_PHASE_BYE))
  {
    text[0] = 0;
    if (m_bAllMissions)
      AddCheatCode (text, cheat_code[3]);
    if (m_bSpeed)
      AddCheatCode (text, cheat_code[4]);
    if (m_bHelp)
      AddCheatCode (text, cheat_code[5]);
    if (m_pDecor->GetInvincible ())
      AddCheatCode (text, cheat_code[6]);
    if (m_pDecor->GetSuper ())
      AddCheatCode (text, cheat_code[7]);

    if (text[0])
    {
      pos.x       = 2;
      pos.y       = 2;
      rect.left   = pos.x;
      rect.right  = pos.x + 300;
      rect.top    = pos.y;
      rect.bottom = pos.y + DIMLITTLEY;
      if (IsRightReading ())
        pos.x = LXIMAGE () - pos.x;
      DrawText (m_pPixmap, pos, text, FONTLITTLE);
    }
  }

  if (m_phase == EV_PHASE_INIT)
  {
    snprintf (
      res, sizeof (res), "%s %u.%u.%u%s", gettext ("Version"), PB_VERSION_MAJOR,
      PB_VERSION_MINOR, PB_VERSION_PATCH, PB_VERSION_EXTRA);
    pos.x = IsRightReading () ? 4 + GetTextWidth (res, FONTLITTLE)
                              : LXIMAGE () - GetTextWidth (res, FONTLITTLE) - 4;
    pos.y = 465;
    DrawText (m_pPixmap, pos, res, FONTLITTLE);

    if (!this->m_updateVersion.empty () && this->m_updateBlinking++ % 80 < 40)
    {
      pos.x = 70 + LXOFFSET ();
      pos.y = 465;
      snprintf (
        res, sizeof (res),
        gettext ("New version available for download on www.blupi.org (v%s)"),
        this->m_updateVersion.c_str ());
      if (IsRightReading ())
        pos.x = LXIMAGE () - pos.x;
      DrawText (m_pPixmap, pos, res, FONTLITTLE);
    }
  }

  if (m_phase == EV_PHASE_SETUP || m_phase == EV_PHASE_SETUPp)
  {
    bEnable = true;
    if (m_speed == 1)
      bEnable = false;
    SetEnable (EV_BUTTON1, bEnable);
    bEnable = true;
    if (m_speed >= (m_bSpeed ? 8 : 2))
      bEnable = false;
    SetEnable (EV_BUTTON2, bEnable);

    volume  = m_pSound->GetAudioVolume ();
    bEnable = true;
    if (volume == 0)
      bEnable = false;
    SetEnable (EV_BUTTON3, bEnable);
    bEnable = true;
    if (volume >= MAXVOLUME)
      bEnable = false;
    SetEnable (EV_BUTTON4, bEnable);

    volume  = m_pSound->GetMidiVolume ();
    bEnable = true;
    if (volume == 0)
      bEnable = false;
    SetEnable (EV_BUTTON5, bEnable);
    bEnable = true;
    if (volume >= MAXVOLUME)
      bEnable = false;
    SetEnable (EV_BUTTON6, bEnable);

    if (m_pMovie->GetEnable ())
    {
      SetEnable (EV_BUTTON7, m_bMovie);
      SetEnable (EV_BUTTON8, !m_bMovie);
    }
    else
    {
      SetEnable (EV_BUTTON7, false);
      SetEnable (EV_BUTTON8, false);
    }

    bEnable = true;
    if (m_scrollSpeed == 0)
      bEnable = false;
    SetEnable (EV_BUTTON9, bEnable);
    bEnable = true;
    if (m_scrollSpeed >= 3)
      bEnable = false;
    SetEnable (EV_BUTTON10, bEnable);
  }

  /* Check if both music formats are available */
  auto ogg = this->IsBaseMusicAvailable (1, "ogg");
  auto mid = this->IsBaseMusicAvailable (1, "mid");

  if (m_phase == EV_PHASE_SETTINGS)
  {
    SetEnable (EV_BUTTON1, m_Lang != m_Languages.begin ());
    SetEnable (EV_BUTTON2, m_Lang != m_Languages.end () - 1);

    SetEnable (EV_BUTTON3, !g_bFullScreen);
    SetEnable (EV_BUTTON4, g_bFullScreen);

    SetEnable (EV_BUTTON5, g_zoom > 1);
    SetEnable (EV_BUTTON6, g_zoom < 2);

    SetEnable (EV_BUTTON7, g_restoreMidi && mid && ogg);
    SetEnable (EV_BUTTON8, !g_restoreMidi && mid && ogg);

    SetEnable (EV_BUTTON9, g_bFullScreen && g_zoom == 1 && g_renderQuality);
    SetEnable (EV_BUTTON10, g_bFullScreen && g_zoom == 1 && !g_renderQuality);

    table[m_index].buttons[4].toolTips[0] =
      g_bFullScreen ? gettext ("Desktop mode") : gettext ("Reduce window size");
    table[m_index].buttons[5].toolTips[0] =
      g_bFullScreen ? gettext ("Legacy mode (640x480)")
                    : gettext ("Increase window size");
  }

  assert (m_index >= 0);

  // Dessine les boutons.
  i = 0;
  while (table[m_index].buttons[i].message != 0)
  {
    m_buttons[i].Draw ();
    ++i;
  }

  if (m_phase == EV_PHASE_PLAY)
  {
    // Dessine les jauges.
    m_pDecor->GetLevelJauge (levels, types);

    for (i = 0; i < 2; i++)
    {
      if (levels[i] < 0)
        m_jauges[i].SetHide (true);
      else
      {
        m_jauges[i].SetHide (false);
        m_jauges[i].SetLevel (levels[i]);
        m_jauges[i].SetType (types[i]);
      }
      m_jauges[i].Draw ();
    }

    // Dessine le menu.
    if (m_menu.IsExist ())
    {
      m_pDecor->BlupiGetButtons (
        m_menuPos, m_menuNb, m_menuButtons, m_menuErrors, m_menuTexts,
        m_menuPerso);
      m_menu.Update (m_menuNb, m_menuButtons, m_menuErrors, m_menuTexts);

      button = m_menu.GetSel ();
      m_pDecor->CelHiliButton (m_menuCel, button);
    }
    m_menu.Draw ();

    auto offset = IsRightReading () ? POSDRAWX + DIMDRAWX : 0;

    // Dessine la rose des vents.
    if (!m_bPause && !m_bDemoPlay)
    {
      DrawTextCenter (gettext ("N"), (10 + 134) / 2 + offset, 17);
      DrawTextCenter (gettext ("S"), (10 + 134) / 2 + offset, 126);
      DrawTextCenter (gettext ("W"), 14 + offset, 70);
      DrawTextCenter (gettext ("E"), 129 + offset, 70);
    }

    // Dessine la pause.
    if (m_bPause)
      DrawTextCenter (gettext ("Game paused"), (10 + 134) / 2 + offset, 20);
    else
    {
      if (m_bDemoRec) // recording demo ?
        DrawTextCenter (gettext ("REC"), (10 + 38) / 2 + offset, 20, FONTRED);
      if (m_bDemoPlay) // playing demo ?
        DrawTextCenter (gettext ("Demo"), (10 + 134) / 2 + offset, 20, FONTRED);
    }

    // Dessine la vitesse.
    pos.x       = IsRightReading () ? LXIMAGE () - 64 : 64;
    pos.y       = LYIMAGE () - 15;
    rect.left   = pos.x;
    rect.right  = pos.x + 20;
    rect.top    = pos.y;
    rect.bottom = pos.y + 15;
    m_pPixmap->DrawPart (-1, CHBACK, pos, rect); // dessine le fond
    if (m_speed > 1)
    {
      snprintf (res, sizeof (res), "x%d", m_speed);
      DrawText (m_pPixmap, pos, res);
    }

    // Dessine le bouton pour les infos.
    if (m_pDecor->GetInfoMode ()) // infos visibles ?
    {
      lg = m_pDecor->GetInfoHeight ();

      pos.x       = POSDRAWX;
      pos.y       = 0;
      rect.left   = POSDRAWX;
      rect.right  = POSDRAWX + DIMDRAWX;
      rect.top    = 0;
      rect.bottom = lg;
      m_pPixmap->DrawPart (-1, CHBACK, pos, rect);

      pos.x       = POSDRAWX;
      pos.y       = lg;
      rect.left   = POSDRAWX;
      rect.right  = POSDRAWX + DIMDRAWX;
      rect.top    = 0;
      rect.bottom = POSDRAWY;
      m_pPixmap->DrawPart (-1, CHBACK, pos, rect);

      pos.x = POSDRAWX_ + 20;
      pos.y = POSDRAWY + 4;
      if (IsRightReading ())
        pos.x = LXIMAGE () - pos.x;
      DrawTextRect (m_pPixmap, pos, m_libelle, 0, FONTLITTLE, 1);
      pos.x = POSDRAWX_ + DIMDRAWX / 2 + 20;
      if (IsRightReading ())
        pos.x = LXIMAGE () - pos.x;
      DrawTextRect (m_pPixmap, pos, m_libelle, 0, FONTLITTLE, 2);

      pos.x = POSDRAWX + DIMDRAWX / 2 - DIMBUTTONX / 2;
      pos.y = lg - 14;
      m_pPixmap->DrawIcon (-1, CHBUTTON, m_bHiliInfoButton ? 73 : 72, pos);

      if (IsHelpHide ()) // bouton pour aide ?
        m_posHelpButton.x = -1;
      else
      {
        m_posHelpButton.x = POSDRAWX + DIMDRAWX - DIMBUTTONX - 2;
        m_posHelpButton.y = lg - DIMBUTTONY - 2;
        if (IsRightReading ())
          m_posHelpButton.x = POSDRAWX + 2;
        m_pPixmap->DrawIcon (
          -1, CHBUTTON, m_bHiliHelpButton ? 2 : 0, m_posHelpButton);
        if (m_bInfoHelp)
          icon = 86; // livre
        else
          icon = 92; // aide
        m_pPixmap->DrawIcon (-1, CHBUTTON, icon, m_posHelpButton);
      }
    }
    else // infos cachées ?
    {
      pos.x = POSDRAWX + DIMDRAWX / 2 - DIMBUTTONX / 2;
      pos.y = -12;
      if (IsRightReading ())
        m_posHelpButton.x = POSDRAWX + 2;
      m_pPixmap->DrawIcon (-1, CHBUTTON, m_bHiliInfoButton ? 75 : 74, pos);
    }
    m_posInfoButton = pos;
  }

  // Dessine les noms des fichiers.
  if (
    m_phase == EV_PHASE_READ || m_phase == EV_PHASE_WRITE ||
    m_phase == EV_PHASE_WRITEp)
  {
    if (m_phase == EV_PHASE_READ)
      snprintf (res, sizeof (res), "%s", gettext ("Open another game"));
    else
      snprintf (res, sizeof (res), "%s", gettext ("Save this game"));

    pos.x = 420 + LXOFFSET ();
    pos.y = 8;
    if (IsRightReading ())
      pos.x = LXIMAGE () - pos.x;
    DrawText (m_pPixmap, pos, res);

    for (i = 0; i < 10; i++)
    {
      world = m_fileWorld[i];
      time  = m_fileTime[i];

      snprintf (text, sizeof (text), "%d", i + 1);
      lg    = GetTextWidth (text);
      pos.x = (420 + 460) / 2 - lg / 2 + LXOFFSET ();
      pos.y = 30 + 12 + 42 * i;
      if (IsRightReading ())
        pos.x = LXIMAGE () - pos.x;
      DrawText (m_pPixmap, pos, text, FONTSLIM);

      pos.x = 420 + 50 + LXOFFSET ();
      if (IsRightReading ())
        pos.x = LXIMAGE () - pos.x;

      if (world >= 0)
      {
        if (world >= 200)
          snprintf (
            text, sizeof (text), gettext ("construction %d, time %d"),
            (world - 200) + 1, time / 100);
        else if (world >= 100)
          snprintf (
            text, sizeof (text), gettext ("mission %d, time %d"),
            (world - 100) + 1, time / 100);
        else
          snprintf (
            text, sizeof (text), gettext ("training %d, time %d"), world + 1,
            time / 100);

        DrawText (m_pPixmap, pos, text); // partie x, temps t
      }
      else
        DrawText (m_pPixmap, pos, gettext ("free slot"), FONTRED); // libre
    }
  }

  // Dessine les réglages pour la fin de la partie.
  if (m_phase == EV_PHASE_TERM)
  {
    Term * pTerm = m_pDecor->GetTerminated ();

    auto x = (10 + 134) / 2 + LXOFFSET ();
    if (IsRightReading ())
      x = LXIMAGE () - x;
    DrawTextCenter (gettext ("Ending conditions"), x, 20);

    pos.x = 170 + 42 * 2 + 4 + LXOFFSET ();
    pos.y = 30 + 12 + 42 * 4;
    snprintf (
      text, sizeof (text), gettext ("Lost if less than %d Blupi"),
      pTerm->nbMinBlupi);
    if (IsRightReading ())
      pos.x = LXIMAGE () - pos.x;
    DrawText (m_pPixmap, pos, text);

    pos.x = 170 + 42 * 2 + 4 + LXOFFSET ();
    pos.y = 30 + 12 + 42 * 5;
    snprintf (
      text, sizeof (text), gettext ("Impossible to win if less than %d Blupi"),
      pTerm->nbMaxBlupi);
    if (IsRightReading ())
      pos.x = LXIMAGE () - pos.x;
    DrawText (m_pPixmap, pos, text);
  }

  // Dessine les textes pour les choix des boutons.
  if (m_phase == EV_PHASE_BUTTON)
  {
    auto x = (10 + 134) / 2 + LXOFFSET ();
    if (IsRightReading ())
      x = LXIMAGE () - x;
    DrawTextCenter (gettext ("Available buttons"), x, 20);
  }

  // Dessine les textes pour le choix des musiques.
  if (m_phase == EV_PHASE_MUSIC)
  {
    auto x = (10 + 134) / 2 + LXOFFSET ();
    if (IsRightReading ())
      x = LXIMAGE () - x;
    DrawTextCenter (gettext ("Music choice"), x, 20);
  }

  // Dessine les textes pour le choix de la région.
  if (m_phase == EV_PHASE_REGION)
  {
    auto x = (10 + 134) / 2 + LXOFFSET ();
    if (IsRightReading ())
      x = LXIMAGE () - x;
    DrawTextCenter (gettext ("Scenery choice"), x, 20);
  }

  // Ajoute "Mission numéro".
  if (m_phase == EV_PHASE_INFO)
  {
    if (m_bSchool)
      snprintf (res, sizeof (res), "%s", gettext ("Training number"));
    else
      snprintf (res, sizeof (res), "%s", gettext ("Mission number"));

    if (m_bPrivate)
      snprintf (res, sizeof (res), "%s", gettext ("Construction number"));

    lg    = GetTextWidth (res);
    pos.x = (140 + 270) / 2 - lg / 2 + LXOFFSET ();
    pos.y = 70;
    if (m_bSchool)
      pos.x -= 40;
    if (m_bPrivate)
      pos.x -= 100;
    if (m_bPrivate)
      pos.y += 14;
    if (IsRightReading ())
      pos.x = LXIMAGE () - pos.x;
    DrawText (m_pPixmap, pos, res, FONTSLIM);
  }

  // Ajoute le texte "Partie interrompue".
  if (m_phase == EV_PHASE_STOP)
  {
    char * text = gettext ("Game paused");
    lg          = GetTextWidth (text);
    pos.x       = (140 + 270) / 2 - lg / 2 + LXOFFSET ();
    pos.y       = 70;
    if (m_bSchool)
      pos.x -= 40;
    if (m_bPrivate)
      pos.x -= 100;
    if (m_bPrivate)
      pos.y += 14;
    if (IsRightReading ())
      pos.x = LXIMAGE () - pos.x;
    DrawText (m_pPixmap, pos, text, FONTRED);
  }

  // Ajoute le texte "Informations complémentaires".
  if (m_phase == EV_PHASE_HELP)
  {
    char * text = gettext ("Help number");
    lg          = GetTextWidth (text);
    pos.x       = (140 + 270) / 2 - lg / 2 + LXOFFSET ();
    pos.y       = 70;
    if (m_bSchool)
      pos.x -= 40;
    if (m_bPrivate)
      pos.x -= 100;
    if (m_bPrivate)
      pos.y += 14;
    if (IsRightReading ())
      pos.x = LXIMAGE () - pos.x;
    DrawText (m_pPixmap, pos, text, FONTRED);
  }

  // Ajoute le numéro du monde.
  if (
    m_phase == EV_PHASE_INFO || m_phase == EV_PHASE_STOP ||
    m_phase == EV_PHASE_HELP)
  {
    if (m_bSchool)
      world = m_exercice;
    else
      world = m_mission;
    if (m_bPrivate)
      world = m_private;

    lg    = GetBignumWidth (world + 1);
    lg    = IsRightReading () ? -lg : lg;
    pos.x = (140 + 270) / 2 - lg / 2 + LXOFFSET ();
    pos.y = 100;
    if (m_bSchool)
      pos.x -= 40;
    if (m_bPrivate)
      pos.x -= 135;
    if (m_bPrivate)
      pos.y = 115;
    if (IsRightReading ())
      pos.x = LXIMAGE () - pos.x;
    DrawBignum (m_pPixmap, pos, world + 1);
  }

  // Affiche facile/difficile.
  if (m_phase == EV_PHASE_INFO)
  {
    if (!m_bSchool)
    {
      std::string text;
      if (m_pDecor->GetSkill () == 0)
      {
        if (m_bPrivate)
        {
          pos.x = 117 + 50;
          pos.y = 115 + 13;
        }
        else
        {
          pos.x = 150 + 50;
          pos.y = 230 + 13;
        }
        pos.x += LXOFFSET ();
        text = gettext ("Easy");
      }
      else if (m_pDecor->GetSkill () == 1)
      {
        if (m_bPrivate)
        {
          pos.x = 117 + 50;
          pos.y = 115 + 42 + 13;
        }
        else
        {
          pos.x = 150 + 50;
          pos.y = 230 + 42 + 13;
        }
        pos.x += LXOFFSET ();
        text = gettext ("Difficult");
      }

      if (IsRightReading ())
        pos.x = LXIMAGE () - pos.x;
      DrawText (m_pPixmap, pos, text.c_str (), FONTSLIM);
    }
  }

  // Affiche le libellé de l'énigme.
  if (
    m_phase == EV_PHASE_INFO || m_phase == EV_PHASE_STOP ||
    m_phase == EV_PHASE_HELP || m_phase == EV_PHASE_HISTORY0 ||
    m_phase == EV_PHASE_HISTORY1)
  {
    pos.x = 355;
    pos.y = 70;
    if (m_bSchool)
      pos.x -= 20;
    if (m_bPrivate)
    {
      pos.x = 460;
      pos.y = 260;
    }
    if (m_bSchool || m_bPrivate)
      pente = 0;
    else
      pente = 19;
    pos.x += LXOFFSET ();
    if (IsRightReading ())
      pos.x = LXIMAGE () - pos.x;
    DrawTextRect (m_pPixmap, pos, m_libelle, pente, FONTSLIM);
  }

  // Affiche le texte lorsque c'est raté.
  if (m_phase == EV_PHASE_LOST)
  {
    const char * list[] = {
      gettext ("You have failed, try again..."),
      gettext ("No, wrong way ..."),
      gettext ("Bang, failed again !"),
      gettext ("Another mistake..."),
      gettext ("No, not that way !"),
    };

    pos.x = 60 + LXOFFSET ();
    pos.y = 443;
    if (IsRightReading ())
      pos.x = LXIMAGE () - pos.x;
    DrawText (m_pPixmap, pos, list[GetWorld () % 5]);
  }

  // Affiche le texte lorsque c'est réussi.
  if (m_phase == EV_PHASE_WIN)
  {
    const char * list[] = {
      gettext ("Well done !"),     gettext ("Yes, great ..."),
      gettext ("Very good."),      gettext ("Excellent..."),
      gettext ("Mission over..."),
    };

    pos.x = 60 + LXOFFSET ();
    pos.y = 443;
    if (IsRightReading ())
      pos.x = LXIMAGE () - pos.x;
    DrawText (m_pPixmap, pos, list[GetWorld () % 5]);
  }

  // Show the ending text when the game is finished.
  if (m_phase == EV_PHASE_LASTWIN)
  {
    char * text;
    if (m_bSchool)
      text = gettext ("Now go on mission.");
    else
      text = gettext ("Very good, success on all missions !");

    if (m_bPrivate)
      text = gettext ("Last construction resolved !");

    pos.x = 60 + LXOFFSET ();
    pos.y = 443;
    if (IsRightReading ())
      pos.x = LXIMAGE () - pos.x;
    DrawText (m_pPixmap, pos, text);
  }

  // Draw the game settings.
  if (m_phase == EV_PHASE_SETUP || m_phase == EV_PHASE_SETUPp)
  {
    DrawTextCenter (gettext ("Global game\nspeed"), 54 + 40 + LXOFFSET (), 80);
    DrawTextCenter (
      gettext ("Scroll speed\nwith mouse"), 169 + 40 + LXOFFSET (), 80);
    DrawTextCenter (
      gettext ("Sound effect\nvolume"), 284 + 40 + LXOFFSET (), 80);
    DrawTextCenter (gettext ("Music\nvolume"), 399 + 40 + LXOFFSET (), 80);
    DrawTextCenter (gettext ("Video\nsequences"), 514 + 40 + LXOFFSET (), 80);

    snprintf (res, sizeof (res), "x%d", m_speed);
    lg    = GetTextWidth (res);
    lg    = IsRightReading () ? -lg : lg;
    pos.x = (54 + 40) - lg / 2 + LXOFFSET ();
    pos.y = 330 - 20;
    DrawText (m_pPixmap, pos, res);

    snprintf (res, sizeof (res), "%d", m_pSound->GetAudioVolume ());
    lg    = GetTextWidth (res);
    lg    = IsRightReading () ? -lg : lg;
    pos.x = (284 + 40) - lg / 2 + LXOFFSET ();
    pos.y = 330 - 20;
    DrawText (m_pPixmap, pos, res);

    snprintf (res, sizeof (res), "%d", m_pSound->GetMidiVolume ());
    lg    = GetTextWidth (res);
    lg    = IsRightReading () ? -lg : lg;
    pos.x = (399 + 40) - lg / 2 + LXOFFSET ();
    pos.y = 330 - 20;
    DrawText (m_pPixmap, pos, res);

    char * text = gettext ("No");
    if (m_pMovie->GetEnable () && m_bMovie)
      text = gettext ("Yes");
    lg    = GetTextWidth (text);
    lg    = IsRightReading () ? -lg : lg;
    pos.x = (514 + 40) - lg / 2 + LXOFFSET ();
    pos.y = 330 - 20;
    DrawText (m_pPixmap, pos, text);

    if (!m_scrollSpeed)
      snprintf (res, sizeof (res), "%s", gettext ("None"));
    else
      snprintf (res, sizeof (res), "%d", m_scrollSpeed);
    lg    = GetTextWidth (res);
    lg    = IsRightReading () ? -lg : lg;
    pos.x = (169 + 40) - lg / 2 + LXOFFSET ();
    pos.y = 330 - 20;
    DrawText (m_pPixmap, pos, res);
  }

  // Draw the settings
  if (m_phase == EV_PHASE_SETTINGS)
  {
    DrawTextCenter (
      gettext ("Interface language\nand sounds"), 54 + 40 + LXOFFSET (), 80);
    DrawTextCenter (
      gettext ("Select the\nwindow mode"), 169 + 40 + LXOFFSET (), 80);
    DrawTextCenter (
      g_bFullScreen ? gettext ("Change the\ndisplay mode")
                    : gettext ("Change the\nwindow size"),
      284 + 40 + LXOFFSET (), 80);
    DrawTextCenter (
      gettext ("Choose the\nmusic format"), 399 + 40 + LXOFFSET (), 80);
    DrawTextCenter (
      gettext ("Change the\nrender quality"), 514 + 40 + LXOFFSET (), 80);

    const auto  locale = GetLocale ();
    std::string lang;
    if (locale == "en")
      lang = "English";
    else if (locale == "en_US")
      lang = "American english";
    else if (locale == "fr")
      lang = "Français";
    else if (locale == "de")
      lang = "Deutsch";
    else if (locale == "it")
      lang = "Italiano";
    else if (locale == "pl")
      lang = "Polski";
    else if (locale == "tr")
      lang = "Türkçe";
    else if (locale == "pt")
      lang = "Português";
    else if (locale == "he")
      lang = "עברית";

    lg    = GetTextWidth (lang.c_str ());
    lg    = IsRightReading () ? -lg : lg;
    pos.x = (54 + 40) - lg / 2 + LXOFFSET ();
    pos.y = 330 - 20;
    DrawText (m_pPixmap, pos, lang.c_str ());

    const char * text =
      g_bFullScreen ? gettext ("Fullscreen") : gettext ("Windowed");
    lg    = GetTextWidth (text);
    lg    = IsRightReading () ? -lg : lg;
    pos.x = (169 + 40) - lg / 2 + LXOFFSET ();
    pos.y = 330 - 20;
    DrawText (m_pPixmap, pos, text);

    if (!g_bFullScreen)
      snprintf (res, sizeof (res), "%dx", g_zoom);
    else
      snprintf (
        res, sizeof (res), "%s",
        g_zoom == 2 ? gettext ("Legacy") : gettext ("Desktop"));

    lg    = GetTextWidth (res);
    lg    = IsRightReading () ? -lg : lg;
    pos.x = (284 + 40) - lg / 2 + LXOFFSET ();
    pos.y = 330 - 20;
    DrawText (m_pPixmap, pos, res);

    text  = (g_restoreMidi && mid) || !ogg ? gettext ("Midi") : gettext ("Ogg");
    lg    = GetTextWidth (text);
    lg    = IsRightReading () ? -lg : lg;
    pos.x = (399 + 40) - lg / 2 + LXOFFSET ();
    pos.y = 330 - 20;
    DrawText (m_pPixmap, pos, text);

    text =
      g_bFullScreen && g_zoom == 1
        ? (g_renderQuality ? gettext ("Anti-aliasing") : gettext ("Aliasing"))
        : gettext ("Not available");
    lg    = GetTextWidth (text);
    lg    = IsRightReading () ? -lg : lg;
    pos.x = (514 + 40) - lg / 2 + LXOFFSET ();
    pos.y = 330 - 20;
    DrawText (m_pPixmap, pos, text);
  }

  // Show the ending text
  if (m_phase == EV_PHASE_BYE)
  {
    char * text;

    text  = gettext ("You have played Planet Blupi.");
    lg    = GetTextWidth (text);
    lg    = IsRightReading () ? -lg : lg;
    pos.x = LXIMAGE () / 2 - lg / 2;
    pos.y = 20;
    DrawText (m_pPixmap, pos, text);

    text  = gettext ("We hope you have had as much fun playing the game as we "
                    "had making it !");
    lg    = GetTextWidth (text);
    lg    = IsRightReading () ? -lg : lg;
    pos.x = LXIMAGE () / 2 - lg / 2;
    pos.y = 40;
    DrawText (m_pPixmap, pos, text);

    static const std::string libs[] = {
      gettext (
        "This game uses statically linked free and open-source libraries:"),
      gettext (" - argagg (MIT)"),
      gettext (" - FFmpeg (LGPLv2.1)"),
      gettext (" - GNU/gettext and GNU/libiconv (GPLv3)"),
      gettext (" - libasound (LGPLv2.1)"),
      gettext (" - libcurl (MIT/X derivate)"),
      gettext (" - libogg and libvorbis (own license)"),
      gettext (" - libpng (own license)"),
      gettext (" - libpulse (LGPLv2.1)"),
      gettext (" - libsndfile (LGPLv3)"),
      gettext (" - SDL_kitchensink (MIT)"),
      gettext (" - SDL2, SDL2_image and SDL2_mixer (zlib license)"),
      gettext (" - zlib (own license)"),
      gettext (
        "All licenses are available under share/doc/planetblupi/copyright")};

    for (size_t i = 0; i < countof (libs); ++i)
    {
      pos.x = 30 + LXOFFSET ();
      if (IsRightReading ())
        pos.x = LXIMAGE () - pos.x;
      pos.y = 120 + i * 20;
      DrawText (m_pPixmap, pos, libs[i].c_str ());
    }

    text  = gettext ("This game is an original creation of Epsitec SA, CH-1400 "
                    "Yverdon-les-Bains");
    lg    = GetTextWidth (text);
    lg    = IsRightReading () ? -lg : lg;
    pos.x = LXIMAGE () / 2 - lg / 2;
    pos.y = 430;
    DrawText (m_pPixmap, pos, text);

    text  = gettext ("http://www.blupi.org            info@blupi.org");
    lg    = GetTextWidth (text);
    lg    = IsRightReading () ? -lg : lg;
    pos.x = LXIMAGE () / 2 - lg / 2;
    pos.y = 450;
    DrawText (m_pPixmap, pos, text);
  }

  // Show the text when the CD-Rom must be inserted (deprecated).
  if (m_phase == EV_PHASE_INSERT)
    DrawTextCenter (
      gettext ("Insert CD-Rom Planet Blupi and wait a few seconds..."),
      LXIMAGE () / 2, 20);

  if (m_phase == EV_PHASE_BUILD)
    SetEnable (EV_PHASE_UNDO, m_pDecor->IsUndo ());

  // Draw the tooltips.
  if (m_textToolTips[0] != 0)
    DrawText (m_pPixmap, m_posToolTips, m_textToolTips);

  return true;
}

/**
 * \brief Return the mouse sprite to use for a position.
 *
 * \param[in] pos - The position.
 * \return the sprite.
 */
MouseSprites
CEvent::MousePosToSprite (Point pos)
{
  MouseSprites sprite;
  bool         bUp = false, bDown = false, bLeft = false, bRight = false;

  sprite = SPRITE_POINTER;

  if (m_phase == EV_PHASE_PLAY || m_phase == EV_PHASE_BUILD)
  {
    if (
      pos.x >= POSMAPX && pos.x <= POSMAPX + DIMMAPX && pos.y >= POSMAPY &&
      pos.y <= POSMAPY + DIMMAPY)
      sprite = SPRITE_MAP;

    if (g_bFullScreen && !m_bDemoRec && !m_bDemoPlay && m_scrollSpeed != 0)
    {
      if (pos.x <= 5 && pos.x >= -2)
        bLeft = true;

      if (pos.x >= LXIMAGE () - 5 && pos.x <= LXIMAGE () + 2)
        bRight = true;

      if (pos.y <= 5 && pos.y >= -2)
        bUp = true;

      if (pos.y >= LYIMAGE () - 5 && pos.y <= LYIMAGE () + 2)
        bDown = true;

      if (bLeft)
        sprite = SPRITE_ARROWL;
      if (bRight)
        sprite = SPRITE_ARROWR;
      if (bUp)
        sprite = SPRITE_ARROWU;
      if (bDown)
        sprite = SPRITE_ARROWD;
      if (bLeft && bUp)
        sprite = SPRITE_ARROWUL;
      if (bLeft && bDown)
        sprite = SPRITE_ARROWDL;
      if (bRight && bUp)
        sprite = SPRITE_ARROWUR;
      if (bRight && bDown)
        sprite = SPRITE_ARROWDR;
    }

    if (this->statDisabled)
      sprite = SPRITE_ARROW;
  }
  else if (m_phase == EV_PHASE_INTRO1)
    sprite = SPRITE_POINTER;
  else if (m_phase == EV_PHASE_BYE)
    sprite = SPRITE_POINTER;
  else
  {
    if (!MouseOnButton (pos))
      sprite = SPRITE_ARROW;
  }

  if (
    m_bFillMouse && // bidon présent ?
    pos.x >= POSDRAWX && pos.x <= POSDRAWX + DIMDRAWX && pos.y >= POSDRAWY &&
    pos.y <= POSDRAWY + DIMDRAWY)
    sprite = SPRITE_FILL;
  if (m_bWaitMouse) // sablier présent ?
    sprite = SPRITE_WAIT;
  if (m_bHideMouse) // souris cachée ?
    sprite = SPRITE_EMPTY;

  return sprite;
}

/**
 * \brief Main mouse sprite handling.
 *
 * \param[in] pos - The position.
 */
void
CEvent::MouseSprite (Point pos)
{
  m_mouseSprite = MousePosToSprite (pos);
  m_pPixmap->ChangeSprite (m_mouseSprite);
}

/**
 * \brief Set or remove the waiting mouse sprite.
 *
 * \param[in] bWait - If waiting.
 */
void
CEvent::WaitMouse (bool bWait)
{
  m_bWaitMouse = bWait;

  if (bWait)
    m_mouseSprite = SPRITE_WAIT;
  else
    m_mouseSprite = MousePosToSprite (GetMousePos ());
  m_pPixmap->SetMouseSprite (m_mouseSprite);
  m_pPixmap->ChangeSprite (m_mouseSprite);
}

/**
 * \brief Hide or show the mouse.
 *
 * \param[in] bHide - If hide.
 */
void
CEvent::HideMouse (bool bHide)
{
  m_bHideMouse = bHide;

  if (bHide)
  {
    m_mouseSprite = SPRITE_EMPTY;
    SDL_ShowCursor (SDL_FALSE);
  }
  else
  {
    m_mouseSprite = MousePosToSprite (GetMousePos ());
    SDL_ShowCursor (SDL_TRUE);
  }

  m_pPixmap->SetMouseSprite (m_mouseSprite);
  m_pPixmap->ChangeSprite (m_mouseSprite);
}

/**
 * \brief Handle events for buttons.
 *
 * \param[in] event - The SDL event.
 * \param[in] pos - The position.
 * \return true if the event is handled.
 */
bool
CEvent::EventButtons (const SDL_Event & event, Point pos)
{
  Sint32 lg;
  Point  _pos = pos;

  // Cherche le tool tips à utiliser pour la souris.
  m_textToolTips[0] = 0;
  m_posToolTips.x   = -1;

  if (IsRightReading ())
    _pos.x = LXIMAGE () - _pos.x;

  if (m_phase == EV_PHASE_PLAY)
  {
    auto progress = [&](CJauge & prog, const char * text) -> bool {
      if (prog.GetHide ())
        return false;

      Point test = prog.GetPos ();
      if (
        pos.x >= test.x && pos.x <= test.x + DIMJAUGEX && pos.y >= test.y &&
        pos.y <= test.y + DIMJAUGEY)
      {
        snprintf (m_textToolTips, sizeof (m_textToolTips), "%s", text);
        lg = GetTextWidth (m_textToolTips);
        lg = IsRightReading () ? -lg : lg;
        test.x += (DIMJAUGEX - lg) / 2;
        test.y += 4;
        m_posToolTips = test;
        return true;
      }
      return false;
    };

    const auto spotted = progress (m_jauges[0], gettext ("Blupi's energy"));
    if (!spotted)
      progress (m_jauges[1], gettext ("Work done"));
  }
  else
  {
    int i = 0;

    assert (m_index >= 0);

    while (table[m_index].buttons[i].message != 0)
    {
      const auto text = m_buttons[i].GetToolTips (pos);
      if (text)
      {
        snprintf (m_textToolTips, sizeof (m_textToolTips), "%s", text);
        lg = GetTextWidth (m_textToolTips);
        pos.x += IsRightReading () ? 0 : 10;
        pos.y += 20;
        if (pos.x > LXIMAGE () + (IsRightReading () ? 0 : -lg))
          pos.x = LXIMAGE () - lg;
        if (pos.x < 0)
          pos.x = 0;
        if (pos.y > LYIMAGE () - 14)
          pos.y = LYIMAGE () - 14;
        m_posToolTips = pos;
        break;
      }
      i++;
    }
  }

  if (m_phase == EV_PHASE_PLAY)
  {
    m_bHiliInfoButton = false;
    if (
      pos.x > m_posInfoButton.x + 6 &&
      pos.x < m_posInfoButton.x + DIMBUTTONX - 6 &&
      pos.y > m_posInfoButton.y + 8 &&
      pos.y < m_posInfoButton.y + DIMBUTTONY - 8)
    {
      m_bHiliInfoButton = true;

      if (
        event.type == SDL_MOUSEBUTTONUP &&
        (event.button.button == SDL_BUTTON_LEFT ||
         event.button.button == SDL_BUTTON_RIGHT))
      {
        m_pSound->PlayImage (
          m_pDecor->GetInfoMode () ? SOUND_CLOSE : SOUND_OPEN, pos);

        // Show or hide the informations at the top.
        m_pDecor->SetInfoMode (!m_pDecor->GetInfoMode ());
      }
    }

    m_bHiliHelpButton = false;
    if (
      m_posHelpButton.x != -1 && pos.x > m_posHelpButton.x &&
      pos.x < m_posHelpButton.x + DIMBUTTONX && pos.y > m_posHelpButton.y &&
      pos.y < m_posHelpButton.y + DIMBUTTONY)
    {
      m_bHiliHelpButton = true;

      if (
        event.type == SDL_MOUSEBUTTONDOWN &&
        (event.button.button == SDL_BUTTON_LEFT ||
         event.button.button == SDL_BUTTON_RIGHT))
        m_pSound->PlayImage (SOUND_CLICK, pos);
      if (
        event.type == SDL_MOUSEBUTTONUP &&
        (event.button.button == SDL_BUTTON_LEFT ||
         event.button.button == SDL_BUTTON_RIGHT))
      {
        // Reverse the help mode in the informations.
        m_bInfoHelp = !m_bInfoHelp;

        if (m_bInfoHelp)
          ReadLibelle (GetWorld (), false, true);
        else
          ReadLibelle (GetWorld () + 2, false, false);
      }
    }
  }

  if (m_phase == EV_PHASE_BUILD)
  {
    if (
      event.type == SDL_MOUSEBUTTONDOWN &&
      (event.button.button == SDL_BUTTON_LEFT ||
       event.button.button == SDL_BUTTON_RIGHT))
    {
      m_pDecor->HideTooltips (true); // Remove tooltips for the decor.
    }
    if (
      event.type == SDL_MOUSEBUTTONUP &&
      (event.button.button == SDL_BUTTON_LEFT ||
       event.button.button == SDL_BUTTON_RIGHT))
      m_pDecor->HideTooltips (false);
  }

  int i = 0;

  assert (m_index >= 0);

  while (table[m_index].buttons[i].message != 0)
  {
    if (m_buttons[i].TreatEvent (event))
      return true;
    i++;
  }

  if (m_phase == EV_PHASE_PLAY)
  {
    if (m_menu.TreatEvent (event))
      return true;
  }

  return false;
}

/**
 * \brief Notify if the mouse is on a button.
 *
 * \param[in] pos - The mouse position.
 * \return true if the mouse is on a button.
 */
bool
CEvent::MouseOnButton (Point pos)
{
  Sint32 i;

  if (m_index < 0)
    return false;

  i = 0;
  while (table[m_index].buttons[i].message != 0)
  {
    if (m_buttons[i].MouseOnButton (pos))
      return true;
    i++;
  }

  return false;
}

/**
 * \brief Return the table index for a specific phase.
 *
 * \param[in] phase - The phase.
 * \return the index in `table`.
 */
Sint32
CEvent::SearchPhase (Uint32 phase)
{
  Sint32 i = 0;

  while (table[i].phase != 0)
  {
    if (table[i].phase == phase)
      return i;
    i++;
  }

  return -1;
}

/**
 * \brief Return the world number.
 *
 * \return the number.
 */
Sint32
CEvent::GetWorld ()
{
  if (m_bPrivate)
    return m_private;
  if (m_bSchool)
    return m_exercice;
  else
    return m_mission;
}

/**
 * \brief Return the physical world number.
 *
 * This number should be the same as the filename.
 *
 * \return the number.
 */
Sint32
CEvent::GetPhysicalWorld ()
{
  if (m_bPrivate)
    return m_private + 200;
  if (m_bSchool)
    return m_exercice;
  else
    return m_mission + 100;
}

Sint32
CEvent::GetImageWorld ()
{
  if (m_bPrivate)
    return 2;
  if (m_bSchool)
    return 0;
  else
    return 1;
}

/**
 * Notify if the help is available.
 *
 * \return true if available.
 */
bool
CEvent::IsHelpHide ()
{
  bool bHide = true;

  if (m_bHelp || m_pDecor->GetTotalTime () > DEF_TIME_HELP)
    bHide = false;
  if (m_bSchool || m_bPrivate)
  {
    bHide = true; // No help for the exercises.
  }

  return bHide;
}

bool
CEvent::IsBaseMusicAvailable (Sint32 music, const std::string & format)
{
  std::string absolute;
  auto        filename =
    string_format ("music/music%.3d.%s", music - 1, format.c_str ());
  return FileExists (filename, absolute, LOCATION_BASE);
}

std::string
CEvent::GetMusicLocation (Sint32 music)
{
  static const std::string exts[] = {"ogg", "mid"};
  static const Location    locs[] = {LOCATION_USER, LOCATION_BASE};
  std::string              absolute;

  // Look for music in the user directory, then in the game directory.
  for (size_t i = 0; i < countof (locs); ++i)
  {
    auto filename = string_format (
      "music/music%.3d.%s", music - 1, exts[g_restoreMidi ? 1 : 0].c_str ());
    if (!FileExists (filename, absolute, locs[i]))
      filename = string_format (
        "music/music%.3d.%s", music - 1, exts[g_restoreMidi ? 0 : 1].c_str ());

    if (FileExists (filename, absolute, locs[i]))
      break;

    absolute = "";
  }

  return absolute;
}

bool
CEvent::LoadBackground ()
{
  Point       totalDim, iconDim;
  std::string filename;
  auto        backWideName = table[m_index].backWideName;

  filename = table[m_index].backName;
  if (filename.find ("%.3d") != std::string::npos)
  {
    auto id  = GetImageWorld ();
    filename = string_format (table[m_index].backName, id);

    if (table[m_index].phase == EV_PHASE_LASTWIN)
    {
      switch (id)
      {
      case 0:
        backWideName = "back-disco.png";
        break;
      case 1:
        backWideName = "back-stars.png";
        break;
      case 2:
        backWideName = "back-win.png";
        break;
      }
    }
  }

  totalDim.x = LXLOGIC ();
  totalDim.y = LYLOGIC ();
  iconDim.x  = 0;
  iconDim.y  = 0;
  return m_pPixmap->Cache (
    CHBACK, filename, totalDim, iconDim, table[m_index].mode, backWideName);
}

/**
 * \brief Change the phase.
 *
 * \param[in] phase - The new phase.
 * \return true if the phase has changed.
 */
bool
CEvent::ChangePhase (Uint32 phase)
{
  Sint32 index, world, time, total, music, i, max;
  char * pButtonExist;
  bool   bEnable, bHide;
  Term * pTerm;

  if (
    phase != EV_PHASE_SETUPp && phase != EV_PHASE_WRITEp &&
    phase != EV_PHASE_PLAY)
    m_pSound->StopMusic ();
  if (phase == EV_PHASE_SETUPp && m_bPause)
    m_pSound->StopMusic ();

  m_textToolTips[0] = 0;
  m_posToolTips.x   = -1;
  m_bPause          = false;
  m_keymod          = 0;
  m_bMouseDown      = false;
  m_debugPos.x      = 0;
  m_debugPos.y      = 0;

  m_pDecor->SetInfoMode (false);
  m_bInfoHelp       = false;
  m_bHiliInfoButton = false;
  m_bHiliHelpButton = false;

  if (phase == EV_PHASE_INTRO1)
    m_introTime = 0;

  if (phase == EV_PHASE_INIT)
    m_demoTime = 0;

  if (phase != EV_PHASE_PLAY)
    DemoRecStop (); // stop recording

  m_pDecor->UndoClose (); // libère le buffer undo

  index = SearchPhase (phase);
  if (index < 0)
    return false;

  HideMouse (false); // montre la souris
  WaitMouse (true);  // met le sablier

  if (m_bBuildModify)
  {
    m_pDecor->InitAfterBuild ();
    m_bBuildModify = false;
  }

  if (m_phase == EV_PHASE_BUILD && phase == EV_PHASE_INFO) // quitte
                                                           // construction ?
  {
    m_pDecor->Write (
      GetPhysicalWorld (), false, GetPhysicalWorld (), 0, 0); // écrit le monde
  }

  // FIXME: pause is better if the game is not stop but just interrupted
  if (m_phase == EV_PHASE_PLAY && m_phase != phase)
  {
    static const std::set<Sint32> except = {SOUND_WIN, SOUND_LOST};
    m_pSound->StopAllSounds (false, &except);
  }

  m_phase = phase; // change phase
  m_index = index;

  if (!this->LoadBackground ())
  {
    WaitMouse (false);
    m_tryInsertCount = 40;
    m_tryPhase       = m_phase;
    return ChangePhase (EV_PHASE_INSERT); // insert the CD-Rom ...
  }

  if (
    m_phase == EV_PHASE_READ || m_phase == EV_PHASE_WRITE ||
    m_phase == EV_PHASE_WRITEp)
  {
    for (i = 0; i < 10; i++)
    {
      if (m_pDecor->FileExist (i, true, world, time, total))
      {
        m_fileWorld[i] = world;
        m_fileTime[i]  = time;
      }
      else
        m_fileWorld[i] = -1;
    }
  }

  if (
    m_phase == EV_PHASE_INFO || m_phase == EV_PHASE_HISTORY0 ||
    m_phase == EV_PHASE_HISTORY1)
  {
    if (
      !m_pDecor->Read (
        GetPhysicalWorld (), false, world, time, total) && // read the world
      !m_bAccessBuild &&
      !m_bPrivate)
    {
      m_tryInsertCount = 40;
      m_tryPhase       = m_phase;
      return ChangePhase (EV_PHASE_INSERT); // insert the CD-Rom ...
    }
    m_pDecor->SetTime (0);
    m_pDecor->SetTotalTime (0);
    m_pDecor->SetInvincible (false);
    m_pDecor->SetSuper (false);
  }

  if (
    m_phase == EV_PHASE_INFO || m_phase == EV_PHASE_STOP ||
    m_phase == EV_PHASE_HELP || m_phase == EV_PHASE_HISTORY0 ||
    m_phase == EV_PHASE_HISTORY1)
  {
    if (m_bPrivate)
      PrivateLibelle ();
    else if (m_phase == EV_PHASE_INFO || m_phase == EV_PHASE_STOP)
    {
      if (m_bSchool)
        ReadLibelle (GetWorld (), m_bSchool, false);
      else
        ReadLibelle (GetWorld () + 2, m_bSchool, false);
    }
    else if (m_phase == EV_PHASE_HELP)
      ReadLibelle (GetWorld (), false, true);
    else
    {
      if (m_phase == EV_PHASE_HISTORY0)
        world = 0;
      else
        world = 1;
      ReadLibelle (world, false, false);
    }
  }

  if (m_phase == EV_PHASE_TESTCD)
  {
    if (m_pDecor->Read (0, false, world, time, total)) // read the world
    {
      return ChangePhase (EV_PHASE_INIT); // ok
    }
    else
    {
      m_tryInsertCount = 40;
      m_tryPhase       = m_phase;
      return ChangePhase (EV_PHASE_INSERT); // insert the CD-Rom ...
    }
  }

  m_jauges[0].SetHide (true);
  m_jauges[1].SetHide (true);
  CreateButtons (phase); // create the buttons accordingly to the phase
  m_bMenu = false;
  m_pDecor->HideTooltips (false);
  m_menu.Delete ();
  m_pDecor->BlupiSetArrow (0, false); // remove all arrows
  m_pDecor->ResetHili ();             // remove all highlights

  if (m_phase == EV_PHASE_PLAY)
  {
    m_pDecor->LoadImages ();
    m_pDecor->SetBuild (false);
    m_pDecor->EnableFog (true);
    m_pDecor->NextPhase (0); // rebuild the map immediatly
    m_pDecor->StatisticInit ();
    m_pDecor->TerminatedInit ();
  }

  if (m_phase == EV_PHASE_BUILD)
  {
    m_bBuildModify = true;
    SetState (EV_DECOR1, 1);
    SetMenu (EV_DECOR1, 0); // grass
    SetMenu (EV_DECOR2, 2); // tree
    SetMenu (EV_DECOR3, 1); // house
    SetMenu (EV_DECOR4, 2); // strong blupi
    SetMenu (EV_DECOR5, 1); // fire
    m_pDecor->LoadImages ();
    m_pDecor->SetBuild (true);
    m_pDecor->EnableFog (false);
    m_pDecor->BlupiDeselect ();
    m_pDecor->NextPhase (0); // rebuild the map immediatly
  }

  if (m_phase == EV_PHASE_INFO)
  {
    bEnable = true;
    if (GetWorld () == 0)
      bEnable = false;
    SetEnable (EV_PREV, bEnable);

    bEnable = true;
    if (m_bAllMissions)
      max = 99;
    else
      max = m_maxMission;
    if (!m_bSchool && GetWorld () >= max)
      bEnable = false;
    if (!m_pDecor->FileExist (
          GetPhysicalWorld () + 1, false, world, time, total))
      bEnable = false;

    if (m_bAccessBuild || m_pDecor->GetTotalTime () > DEF_TIME_HELP * 6)
      bEnable = true;

    if (GetWorld () >= 99)
      bEnable = false;

    if (m_bPrivate)
      bEnable = GetWorld () < 20 - 1;
    SetEnable (EV_NEXT, bEnable);

    bHide = true;
    if (m_bAccessBuild || m_bPrivate)
      bHide = false;
    SetHide (EV_PHASE_BUILD, bHide);

    if (m_bSchool)
    {
      SetHide (EV_PHASE_SKILL1, true);
      SetHide (EV_PHASE_SKILL2, true);
    }
    else
    {
      SetState (EV_PHASE_SKILL1, m_pDecor->GetSkill () == 0 ? 1 : 0);
      SetState (EV_PHASE_SKILL2, m_pDecor->GetSkill () == 1 ? 1 : 0);
    }
  }

  if (m_phase == EV_PHASE_STOP)
    SetHide (EV_PHASE_HELP, IsHelpHide ());

  if (m_phase == EV_PHASE_READ)
  {
    for (i = 0; i < 10; i++)
    {
      if (m_fileWorld[i] == -1)
        SetEnable (EV_READ0 + i, false);
    }
  }

  if (m_phase == EV_PHASE_BUTTON)
  {
    pButtonExist = m_pDecor->GetButtonExist ();

    for (i = 0; i < MAXBUTTON; i++)
      SetState (EV_BUTTON0 + i, pButtonExist[i]);
  }

  if (m_phase == EV_PHASE_TERM)
  {
    pTerm = m_pDecor->GetTerminated ();

    SetState (EV_BUTTON1, pTerm->bHachBlupi ? 1 : 0);
    SetState (EV_BUTTON2, pTerm->bHachPlanche ? 1 : 0);
    SetState (EV_BUTTON3, pTerm->bStopFire ? 1 : 0);
    SetState (EV_BUTTON8, pTerm->bHomeBlupi ? 1 : 0);
    SetState (EV_BUTTON9, pTerm->bKillRobots ? 1 : 0);
    SetState (EV_BUTTON10, pTerm->bHachTomate ? 1 : 0);
    SetState (EV_BUTTON11, pTerm->bHachMetal ? 1 : 0);
    SetState (EV_BUTTON12, pTerm->bHachRobot ? 1 : 0);
  }

  if (m_phase == EV_PHASE_MUSIC)
  {
    music = m_pDecor->GetMusic ();

    for (i = 0; i < 11; i++)
      SetState (EV_BUTTON1 + i, music == i ? 1 : 0);
  }

  if (m_phase == EV_PHASE_REGION)
  {
    music = m_pDecor->GetRegion ();

    for (i = 0; i < 4; i++)
      SetState (EV_BUTTON1 + i, music == i ? 1 : 0);
  }

  if (m_phase == EV_PHASE_PLAY || m_phase == EV_PHASE_MUSIC)
  {
    if (m_pSound->IsPlayingMusic ())
    {
      m_pSound->AdaptVolumeMusic ();
    }
    else
    {
      music = m_pDecor->GetMusic ();
      if (music > 0)
      {
        auto absolute = this->GetMusicLocation (music);

        m_pSound->StopMusic ();
        m_pSound->PlayMusic (absolute);
      }
    }
  }

  if (phase == EV_PHASE_H0MOVIE)
  {
    m_movieToStart    = "movie/history0.mkv";
    m_phaseAfterMovie = EV_PHASE_HISTORY0;
  }

  if (phase == EV_PHASE_H1MOVIE)
  {
    m_movieToStart    = "movie/history1.mkv";
    m_phaseAfterMovie = EV_PHASE_HISTORY1;
  }

  if (phase == EV_PHASE_H2MOVIE)
  {
    m_movieToStart    = "movie/history2.mkv";
    m_phaseAfterMovie = EV_PHASE_INFO;
  }

  if (phase == EV_PHASE_PLAYMOVIE)
  {
    m_movieToStart = string_format ("movie/play%.3d.mkv", GetPhysicalWorld ());
    m_phaseAfterMovie = EV_PHASE_PLAY;
  }

  if (phase == EV_PHASE_WINMOVIE)
  {
    m_movieToStart = string_format ("movie/win%.3d.mkv", GetPhysicalWorld ());
    m_phaseAfterMovie = EV_PHASE_WIN;

    if (
      (m_bPrivate && GetPhysicalWorld () - 200 == MAX_PRIVATE_MISSIONS - 1) ||
      (!m_bPrivate &&
       m_pDecor->FileExist (GetPhysicalWorld (), false, world, time, total) &&
       !m_pDecor->FileExist (
         GetPhysicalWorld () + 1, false, world, time, total)))
      m_phaseAfterMovie = EV_PHASE_LASTWIN;
  }

  WaitMouse (false);
  return true;
}

/**
 * \brief Return the current phase.
 *
 * \return the phase number.
 */
Uint32
CEvent::GetPhase ()
{
  return m_phase;
}

/**
 * \brief Try to read the CD-Rom.
 */
void
CEvent::TryInsert ()
{
  if (m_tryInsertCount == 0)
    ChangePhase (m_tryPhase);
  else
    m_tryInsertCount--;
}

/**
 * \brief Start a movie if necessary.
 *
 * \return true if the movie has started.
 */
bool
CEvent::MovieToStart ()
{
  bool movie = false;

  if (m_movieToStart[0] != 0) // is movie available?
  {
    if (StartMovie (m_movieToStart))
    {
      movie   = true;
      m_phase = m_phaseAfterMovie; // the next normal phase
    }
    else
      ChangePhase (m_phaseAfterMovie);

    m_movieToStart[0] = 0;
  }

  return movie;
}

/**
 * \brief Shift the decor.
 *
 * \param[in] dx - Delta x.
 * \param[in] dy - Delta y.
 */
void
CEvent::DecorShift (Sint32 dx, Sint32 dy)
{
  Point corner;

  if (m_phase != EV_PHASE_PLAY && m_phase != EV_PHASE_BUILD)
    return;

  corner = m_pDecor->GetCorner ();

  corner.x += dx;
  corner.y += dy;

  m_pDecor->SetCorner (corner);
}

/**
 * \brief Shift the decor when the mouse is on the sides.
 */
void
CEvent::DecorAutoShift ()
{
  Sint32 max, maxLimit = 4, xMoveFactor = 1, yMoveFactor = 1, vectorFactor = 1;
  Point  offset;
  Uint32 dir = 0;

  bool byKeyboard = !!this->shiftDirection;

  if (byKeyboard)
  {
    dir          = this->shiftDirection;
    xMoveFactor  = 2;
    yMoveFactor  = 3;
    vectorFactor = 2;
    if (m_scrollSpeed == 1)
      maxLimit = 5; // 4..2..1
  }
  else
  {
    if (m_bDemoRec || m_bDemoPlay)
      return;

    switch (m_mouseSprite)
    {
    case SPRITE_ARROWL:
      dir = DIRECTION_LEFT;
      break;

    case SPRITE_ARROWR:
      dir = DIRECTION_RIGHT;
      break;

    case SPRITE_ARROWU:
      dir = DIRECTION_UP;
      break;

    case SPRITE_ARROWD:
      dir = DIRECTION_DOWN;
      break;

    case SPRITE_ARROWUL:
      dir = DIRECTION_UP | DIRECTION_LEFT;
      break;

    case SPRITE_ARROWUR:
      dir = DIRECTION_UP | DIRECTION_RIGHT;
      break;

    case SPRITE_ARROWDL:
      dir = DIRECTION_DOWN | DIRECTION_LEFT;
      break;

    case SPRITE_ARROWDR:
      dir = DIRECTION_DOWN | DIRECTION_RIGHT;
      break;

    default:
      break;
    }
  }

  m_bShift = false;

  if (!byKeyboard && (!g_bFullScreen || m_scrollSpeed == 0))
    return;

  max = maxLimit - m_scrollSpeed; // max <- 3..1

  if (m_phase == EV_PHASE_PLAY || m_phase == EV_PHASE_BUILD)
  {
    if (m_shiftPhase == 0) // start shift ?
    {
      switch (dir)
      {
      case DIRECTION_LEFT:
        m_shiftOffset.x = +2;
        m_shiftOffset.y = 0;
        m_shiftVector.x = -1;
        m_shiftVector.y = +1;
        break;

      case DIRECTION_RIGHT:
        m_shiftOffset.x = -2;
        m_shiftOffset.y = 0;
        m_shiftVector.x = +1;
        m_shiftVector.y = -1;
        break;

      case DIRECTION_UP:
        m_shiftOffset.x = 0;
        m_shiftOffset.y = +2;
        m_shiftVector.x = -1;
        m_shiftVector.y = -1;
        if (vectorFactor > 1)
          ++vectorFactor;
        break;

      case DIRECTION_DOWN:
        m_shiftOffset.x = 0;
        m_shiftOffset.y = -2;
        m_shiftVector.x = +1;
        m_shiftVector.y = +1;
        if (vectorFactor > 1)
          ++vectorFactor;
        break;

      default:
        if (dir == (DIRECTION_UP | DIRECTION_LEFT))
        {
          m_shiftOffset.x = +1;
          m_shiftOffset.y = +1;
          m_shiftVector.x = -1;
          m_shiftVector.y = 0;
          if (vectorFactor > 1)
            ++vectorFactor;
        }
        else if (dir == (DIRECTION_UP | DIRECTION_RIGHT))
        {
          m_shiftOffset.x = -1;
          m_shiftOffset.y = +1;
          m_shiftVector.x = 0;
          m_shiftVector.y = -1;
          if (vectorFactor > 1)
            ++vectorFactor;
        }
        else if (dir == (DIRECTION_DOWN | DIRECTION_LEFT))
        {
          m_shiftOffset.x = +1;
          m_shiftOffset.y = -1;
          m_shiftVector.x = 0;
          m_shiftVector.y = +1;
          if (vectorFactor > 1)
            ++vectorFactor;
        }
        else if (dir == (DIRECTION_DOWN | DIRECTION_RIGHT))
        {
          m_shiftOffset.x = -1;
          m_shiftOffset.y = -1;
          m_shiftVector.x = +1;
          m_shiftVector.y = 0;
          if (vectorFactor > 1)
            ++vectorFactor;
        }
        else
        {
          m_shiftOffset.x = 0;
          m_shiftOffset.y = 0;
          m_shiftVector.x = 0;
          m_shiftVector.y = 0;
        }
        break;
      }

      m_shiftOffset.x *= xMoveFactor;
      m_shiftOffset.y *= yMoveFactor;
      m_shiftVector.x *= vectorFactor;
      m_shiftVector.y *= vectorFactor;

      if (m_shiftVector.x != 0 || m_shiftVector.y != 0)
        m_shiftPhase = max;
    }

    if (m_shiftPhase > 0)
    {
      m_bShift = true;
      m_shiftPhase--;

      offset.x = m_shiftOffset.x * (max - m_shiftPhase) * (DIMCELX / 2 / max);
      offset.y = m_shiftOffset.y * (max - m_shiftPhase) * (DIMCELY / 2 / max);
      m_pDecor->SetShiftOffset (offset);

      if (m_shiftPhase == 0) // last phase ?
      {
        this->shiftDirection = 0;
        offset.x             = 0;
        offset.y             = 0;
        m_pDecor->SetShiftOffset (offset);
        DecorShift (m_shiftVector.x, m_shiftVector.y);
      }
    }
  }
}

/**
 * \brief Notify if a shift is doing.
 *
 * \return true of the shift is doing.
 */
bool
CEvent::IsShift ()
{
  return m_bShift;
}

// Modifie le décor lorsque le bouton de la souris est pressé.

bool
CEvent::PlayDown (Point pos, const SDL_Event & event)
{
  bool    bDecor = false;
  bool    bMap   = false;
  Sint32  rank, h;
  Buttons button;
  Point   cel;

  m_pDecor->BlupiSetArrow (0, false); // enlève toutes les flèches

  m_bMouseDown = false;

  if (m_bMenu)
  {
    m_menu.Message ();
    m_bMenu = false;
    m_pDecor->HideTooltips (false);
    m_menu.Delete ();
    return true;
  }

  m_pDecor->StatisticDown (pos);

  if (
    pos.x >= POSMAPX && pos.x <= POSMAPX + DIMMAPX && pos.y >= POSMAPY &&
    pos.y <= POSMAPY + DIMMAPY)
    bMap = true;
  h = m_pDecor->GetInfoHeight () + POSDRAWY;
  if (
    pos.x >= POSDRAWX && pos.x <= POSDRAWX + DIMDRAWX && pos.y >= h &&
    pos.y <= h + DIMDRAWY)
    bDecor = true;

  if (!bDecor && !bMap)
    return false;

  cel = m_pDecor->ConvPosToCel (pos, true);
  if (event.button.button == SDL_BUTTON_RIGHT)
  {
    if (bMap)
      button = BUTTON_GO;
    else
      button = m_pDecor->GetDefButton (cel);
    m_pDecor->BlupiGoal (cel, button);
    return true;
  }

  if (bMap)
  {
    m_pDecor->SetCorner (cel, true);
    m_pDecor->NextPhase (0); // faudra refaire la carte tout de suite
    return true;
  }

  rank = m_pDecor->GetTargetBlupi (pos);
  if (rank >= 0 && !m_pDecor->IsWorkBlupi (rank))
  {
    m_bHili      = true;
    m_bMouseDown = true;
    m_pDecor->BlupiHiliDown (pos, !!(m_keymod & KMOD_SHIFT));
  }
  else
  {
    m_bHili      = false;
    m_bMouseDown = true;
  }

  return true;
}

// Modifie le décor lorsque la souris est déplacée.

bool
CEvent::PlayMove (Point pos)
{
  if (m_bMenu)
  {
    if (!m_menu.IsExist ())
    {
      m_bMenu = false;
      m_pDecor->HideTooltips (false);
      m_menu.Delete ();
    }
    return true;
  }

  m_pDecor->StatisticMove (pos, this->statDisabled);

  if (m_bMouseDown) // bouton souris pressé ?
  {
    if (m_bHili)
      m_pDecor->BlupiHiliMove (pos);
    else
      m_pDecor->CelHili (pos, 0);
  }
  else
    m_pDecor->CelHili (pos, 0);

  return true;
}

// Modifie le décor lorsque le bouton de la souris est relâché.

bool
CEvent::PlayUp (Point pos)
{
  static Sounds table_sound_boing[] = {
    SOUND_BOING1,
    SOUND_BOING2,
    SOUND_BOING3,
  };

  m_pDecor->StatisticUp (pos);

  if (m_bMouseDown) // bouton souris pressé ?
  {
    if (m_bHili)
      m_pDecor->BlupiHiliUp (pos);
    else
    {
      m_pDecor->BlupiGetButtons (
        pos, m_menuNb, m_menuButtons, m_menuErrors, m_menuTexts, m_menuPerso);
      if (m_menuNb == 0)
        m_pDecor->BlupiSound (
          -1, table_sound_boing[Random (0, countof (table_sound_boing) - 1)],
          pos);
      else
      {
        m_menuCel = m_pDecor->ConvPosToCel (pos);
        m_menuPos = pos;
        m_menu.Create (
          m_pPixmap, m_pSound, this, pos, m_menuNb, m_menuButtons, m_menuErrors,
          m_menuTexts, m_menuPerso);
        m_bMenu = true;
        m_pDecor->HideTooltips (true); // plus de tooltips pour décor
      }
    }
  }

  m_bMouseDown = false;

  return true;
}

Language
CEvent::GetStartLanguage ()
{
  if (this->m_LangStart == "en_US")
    return Language::en_US;
  if (this->m_LangStart == "fr")
    return Language::fr;
  if (this->m_LangStart == "de")
    return Language::de;
  if (this->m_LangStart == "it")
    return Language::it;
  if (this->m_LangStart == "pl")
    return Language::pl;
  if (this->m_LangStart == "tr")
    return Language::tr;
  if (this->m_LangStart == "pt")
    return Language::pt;
  if (this->m_LangStart == "he")
    return Language::he;
  return Language::en;
}

Language
CEvent::GetLanguage ()
{
  return *this->m_Lang;
}

void
CEvent::SetLanguage (Language lang)
{
  static char  env[64];
  const char * slang;

  if (lang != Language::undef)
    m_Lang = m_Languages.begin () + static_cast<int> (lang);

  switch (*m_Lang)
  {
  default:
  case Language::undef:
  case Language::en:
    slang = "C";
    break;
  case Language::en_US:
    slang = "en_US";
    break;
  case Language::fr:
    slang = "fr";
    break;
  case Language::de:
    slang = "de";
    break;
  case Language::it:
    slang = "it";
    break;
  case Language::pl:
    slang = "pl";
    break;
  case Language::tr:
    slang = "tr";
    break;
  case Language::pt:
    slang = "pt";
    break;
  case Language::he:
    slang = "he";
    break;
  }

  snprintf (env, sizeof (env), "LANGUAGE=%s", slang);

  {
    putenv (env);
#ifndef EMSCRIPTEN
    extern int _nl_msg_cat_cntr;
    ++_nl_msg_cat_cntr;
#endif /* EMSCRIPTEN */
  }

  SDL_SetWindowTitle (g_window, gettext ("Planet Blupi"));

  m_pSound->CacheAll ();
}

// Clic dans un bouton.
// Message = EV_BUTTON0..EV_BUTTON39

void
CEvent::ChangeButtons (Sint32 message)
{
  Buttons button;
  Sint32  state, volume, max;
  char *  pButtonExist;
  Term *  pTerm;

  if (m_phase == EV_PHASE_PLAY)
  {
    button = m_menuButtons[message - EV_BUTTON0];
    m_pDecor->BlupiGoal (m_menuCel, button);
  }

  if (m_phase == EV_PHASE_BUTTON)
  {
    pButtonExist = m_pDecor->GetButtonExist ();

    state = GetState (message);
    if (state == 0)
      state = 1;
    else
      state = 0;
    SetState (message, state); // pressé <-> relâché

    pButtonExist[message - EV_BUTTON0] = state;
    pButtonExist[BUTTON_DJEEP]         = true;
    pButtonExist[BUTTON_DARMOR]        = true;
  }

  if (m_phase == EV_PHASE_TERM)
  {
    pTerm = m_pDecor->GetTerminated ();

    if (
      message == EV_BUTTON1 || message == EV_BUTTON2 || message == EV_BUTTON3 ||
      message == EV_BUTTON8 || message == EV_BUTTON9 ||
      message == EV_BUTTON10 || message == EV_BUTTON11 ||
      message == EV_BUTTON12)
    {
      state = GetState (message);
      if (state == 0)
        state = 1;
      else
        state = 0;
      SetState (message, state); // pressé <-> relâché

      if (message == EV_BUTTON1)
        pTerm->bHachBlupi = state;
      if (message == EV_BUTTON2)
        pTerm->bHachPlanche = state;
      if (message == EV_BUTTON3)
        pTerm->bStopFire = state;
      if (message == EV_BUTTON8)
        pTerm->bHomeBlupi = state;
      if (message == EV_BUTTON9)
        pTerm->bKillRobots = state;
      if (message == EV_BUTTON10)
        pTerm->bHachTomate = state;
      if (message == EV_BUTTON11)
        pTerm->bHachMetal = state;
      if (message == EV_BUTTON12)
        pTerm->bHachRobot = state;
    }

    if (message == EV_BUTTON4)
    {
      if (pTerm->nbMinBlupi > 1)
        pTerm->nbMinBlupi--;
    }
    if (message == EV_BUTTON5)
    {
      if (pTerm->nbMinBlupi < 100)
        pTerm->nbMinBlupi++;
    }

    if (message == EV_BUTTON6)
    {
      if (pTerm->nbMaxBlupi > 1)
        pTerm->nbMaxBlupi--;
    }
    if (message == EV_BUTTON7)
    {
      if (pTerm->nbMaxBlupi < 100)
        pTerm->nbMaxBlupi++;
    }
  }

  if (m_phase == EV_PHASE_MUSIC)
  {
    m_pDecor->SetMusic (message - EV_BUTTON1);
    ChangePhase (m_phase);
  }

  if (m_phase == EV_PHASE_REGION)
  {
    m_pDecor->SetRegion (message - EV_BUTTON1);
    ChangePhase (EV_PHASE_BUILD);
  }

  if (m_phase == EV_PHASE_SETUP || m_phase == EV_PHASE_SETUPp)
  {
    if (message == EV_BUTTON1)
    {
      if (m_speed > 1)
        m_speed = m_speed >> 1;
    }
    if (message == EV_BUTTON2)
    {
      if (m_bSpeed)
        max = 8;
      else
        max = 2;
      if (m_speed < max)
        m_speed = m_speed << 1;
    }

    if (message == EV_BUTTON3)
    {
      volume = m_pSound->GetAudioVolume ();
      if (volume > 0)
        m_pSound->SetAudioVolume (volume - 1);
    }
    if (message == EV_BUTTON4)
    {
      volume = m_pSound->GetAudioVolume ();
      if (volume < MAXVOLUME)
        m_pSound->SetAudioVolume (volume + 1);
    }

    if (message == EV_BUTTON5)
    {
      volume = m_pSound->GetMidiVolume ();
      if (volume > 0)
      {
        m_pSound->SetMidiVolume (volume - 1);
        m_pSound->AdaptVolumeMusic ();
      }
    }
    if (message == EV_BUTTON6)
    {
      volume = m_pSound->GetMidiVolume ();
      if (volume < MAXVOLUME)
      {
        m_pSound->SetMidiVolume (volume + 1);
        m_pSound->AdaptVolumeMusic ();
      }
    }

    if (message == EV_BUTTON7)
      m_bMovie = false;
    if (message == EV_BUTTON8)
      m_bMovie = true;

    if (message == EV_BUTTON9)
    {
      if (m_scrollSpeed > 0)
        m_scrollSpeed--;
    }
    if (message == EV_BUTTON10)
    {
      if (m_scrollSpeed < 3)
        m_scrollSpeed++;
    }
  }

  if (m_phase == EV_PHASE_SETTINGS)
  {
    switch (message)
    {
    case EV_BUTTON1:
      if (m_Lang != m_Languages.begin ())
        --m_Lang;
      SetLanguage ();
      break;
    case EV_BUTTON2:
      if (m_Lang != m_Languages.end () - 1)
        ++m_Lang;
      SetLanguage ();
      break;
    case EV_BUTTON3:
    {
      auto zoom = g_zoom;
      g_zoom    = g_settingsOverload & SETTING_LEGACY ? 2 : 1;
      SetFullScreen (true, zoom);
      break;
    }
    case EV_BUTTON4:
    {
      Sint32 w1;
      SDL_GetWindowSize (g_window, &w1, nullptr);
      SetFullScreen (false);
      SetWindowSize (g_zoom * static_cast<double> (w1) / LXIMAGE (), g_zoom);
      break;
    }
    case EV_BUTTON5:
    {
      auto scale = g_zoom;
      if (g_zoom > 1)
        --g_zoom;

      if (g_bFullScreen && scale == 2)
      {
        SDL_Event ev;
        ev.type = SDL_QUIT;
        SDL_PushEvent (&ev);
        g_restart = RestartMode::DESKTOP;
        break;
      }

      SetWindowSize (scale, g_zoom);
      if (g_bFullScreen)
        SetFullScreen (g_bFullScreen);
      break;
    }
    case EV_BUTTON6:
    {
      auto scale = g_zoom;
      if (g_zoom < 2)
        ++g_zoom;

      if (g_bFullScreen && g_zoom == 2)
      {
        SDL_Event ev;
        ev.type = SDL_QUIT;
        SDL_PushEvent (&ev);
        g_restart = RestartMode::LEGACY;
        break;
      }

      SetWindowSize (scale, g_zoom);
      if (g_bFullScreen)
        SetFullScreen (g_bFullScreen);
      break;
    }
    case EV_BUTTON7:
      g_restoreMidi = false;
      break;
    case EV_BUTTON8:
      g_restoreMidi = true;
      break;
    case EV_BUTTON9:
      g_renderQuality = false;
      this->m_pPixmap->CreateMainTexture ();
      break;
    case EV_BUTTON10:
      g_renderQuality = true;
      this->m_pPixmap->CreateMainTexture ();
      break;
    }
  }
}

// Met un sol si nécessaire sous un objet.

void
CEvent::BuildFloor (Point cel, Sint32 insIcon)
{
  Sint32 iFloor, channel, icon;

  if (insIcon == -1)
    return; // supprime ?

  if (
    insIcon < 6 ||  // petite plante ?
    insIcon == 117) // bateau ?
    return;

  iFloor = 1; // herbe

  if (
    insIcon == 28 ||  // laboratoire ?
    insIcon == 61 ||  // cabane de jardin ?
    insIcon == 113 || // maison ?
    insIcon == 120)   // usine ?
  {
    iFloor = 16; // sol brun foncé
  }

  if (insIcon == 122) // mine de fer ?
  {
    iFloor = 71; // sol avec minerai
  }

  if (
    insIcon == 99 ||                      // station de recharge ?
    insIcon == 100 ||                     // usine ennemie ?
    insIcon == 102 ||                     // usine ennemie ?
    insIcon == 104 ||                     // usine ennemie ?
    (insIcon >= 106 && insIcon <= 112) || // barrière ?
    insIcon == 115 ||                     // usine ennemie ?
    insIcon == 17 ||                      // usine ennemie ?
    insIcon == 12)                        // fusée ?
  {
    iFloor = 67; // sol bleu ennemi
  }

  m_pDecor->GetFloor (cel, channel, icon);
  if (
    (channel == CHFLOOR && icon >= 2 && icon <= 14) || // eau ou rive ?
    iFloor != 1)
  {
    m_pDecor->PutFloor (cel, CHFLOOR, iFloor); // met un sol
    m_pDecor->ArrangeFloor (cel);
  }
}

// Enlève si nécessaire un objet sur l'eau.

void
CEvent::BuildWater (Point cel, Sint32 insIcon)
{
  Sint32 channel, icon;

  if (insIcon != 14)
    return; // rien à faire si pas eau

  m_pDecor->GetObject (cel, channel, icon);
  if (
    channel == CHOBJECT && icon >= 6 && // objet (pas petite plante) ?
    icon != 117)                        // pas bateau ?
  {
    m_pDecor->PutObject (cel, -1, -1); // enlève l'objet
    m_pDecor->ArrangeObject (cel);
  }
}

// Cette table donne les objets à construire en fonction
// du choix dans le menu.

static Sint32 tableFloor[] = {
  1,  49, 50, 51, 0,  0,  0,  0, 0, 0, // 0 herbe
  20, 66, 79, 0,  0,  0,  0,  0, 0, 0, // 1 foncé
  33, 46, 47, 48, 71, 0,  0,  0, 0, 0, // 2 terre
  14, 0,  0,  0,  0,  0,  0,  0, 0, 0, // 3 mer
  15, 16, 17, 18, 19, 65, 67, 0, 0, 0, // 4 dalles
  52, 80, 0,  0,  0,  0,  0,  0, 0, 0, // 5 couveuse
};

static Sint32 tableObject[] = {
  -1, 0,  0,  0, 0,  0,  0, 0, 0, 0, // 0 détruit
  0,  4,  1,  2, 3,  5,  0, 0, 0, 0, // 1 plantes
  6,  7,  8,  9, 10, 11, 0, 0, 0, 0, // 2 arbres
  81, 83, 94, 0, 0,  0,  0, 0, 0, 0, // 5 fleurs
};

static Sint32 tableHome[] = {
  -1,  0,   0,   0,   0,   0,   0,  0, 0, 0, //  0 détruit
  113, 61,  28,  120, 0,   0,   0,  0, 0, 0, //  1 maison
  27,  0,   0,   0,   0,   0,   0,  0, 0, 0, //  2 tour de protection
  122, 0,   0,   0,   0,   0,   0,  0, 0, 0, //  3 mine de fer
  99,  100, 102, 104, 115, 17,  12, 0, 0, 0, //  4 ennemi
  112, 0,   0,   0,   0,   0,   0,  0, 0, 0, //  5 barrière
  26,  71,  0,   0,   0,   0,   0,  0, 0, 0, //  6 palissade
  37,  38,  39,  40,  41,  42,  43, 0, 0, 0, //  7 rochers
  36,  44,  60,  63,  80,  123, 14, 0, 0, 0, //  8 matières
  85,  125, 93,  92,  0,   0,   0,  0, 0, 0, //  9 pièges
  117, 118, 16,  0,   0,   0,   0,  0, 0, 0, // 10 véhicules
};

// Modifie le décor lorsque le bouton de la souris est pressé.

bool
CEvent::BuildDown (Point pos, Uint16 mod, const SDL_Event * event, bool bMix)
{
  Point  cel;
  Sint32 menu, channel, icon;

  if (bMix && m_pDecor->MapMove (pos))
    return true;

  if (
    pos.x < POSDRAWX || pos.x > POSDRAWX + DIMDRAWX || pos.y < POSDRAWY ||
    pos.y > POSDRAWY + DIMDRAWY)
    return false;

  bool isRightClick = event ? event->button.button == SDL_BUTTON_RIGHT : false;

  if (bMix)
  {
    m_pDecor->UndoCopy (); // copie le décor pour undo év.
  }

  if (GetState (EV_DECOR1) == 1 && !isRightClick) // pose d'un sol
  {
    cel  = m_pDecor->ConvPosToCel2 (pos);
    menu = GetMenu (EV_DECOR1);

    if (!m_pDecor->GetFloor (cel, channel, icon))
      return false;

    if (bMix && tableFloor[menu * 10 + m_lastFloor[menu]] == icon)
    {
      m_lastFloor[menu]++;
      if (tableFloor[menu * 10 + m_lastFloor[menu]] == 0)
        m_lastFloor[menu] = 0;
    }

    if (mod & KMOD_CTRL) // touche Ctrl enfoncée ?
    {
      WaitMouse (true);
      m_pDecor->ArrangeFill (
        cel, CHFLOOR, tableFloor[menu * 10 + m_lastFloor[menu]], true);
      WaitMouse (false);
    }
    else
    {
      icon = tableFloor[menu * 10 + m_lastFloor[menu]];
      if (menu >= 1) // met un sol ?
      {
        BuildWater (cel, icon); // enlève les objets
      }
      m_pDecor->PutFloor (cel, CHFLOOR, icon);
      m_pDecor->ArrangeFloor (cel);
    }
  }

  if (GetState (EV_DECOR2) == 1 || isRightClick) // pose d'un objet
  {
    cel  = m_pDecor->ConvPosToCel2 (pos);
    menu = isRightClick ? 0 : GetMenu (EV_DECOR2);

    if (!m_pDecor->GetObject (cel, channel, icon))
      return false;

    if (bMix && tableObject[menu * 10 + m_lastObject[menu]] == icon)
    {
      m_lastObject[menu]++;
      if (tableObject[menu * 10 + m_lastObject[menu]] == 0)
        m_lastObject[menu] = 0;
    }

    if (mod & KMOD_CTRL) // touche Ctrl enfoncée ?
    {
      WaitMouse (true);
      m_pDecor->ArrangeFill (
        cel, CHOBJECT, tableObject[menu * 10 + m_lastObject[menu]], false);
      WaitMouse (false);
    }
    else
    {
      icon = tableObject[menu * 10 + m_lastObject[menu]];
      BuildFloor (cel, icon); // met un sol si nécessaire
      m_pDecor->PutObject (cel, CHOBJECT, icon);
      m_pDecor->ArrangeObject (cel);
    }
  }

  if (GetState (EV_DECOR3) == 1 || isRightClick) // pose d'un batiment
  {
    cel  = m_pDecor->ConvPosToCel2 (pos);
    menu = isRightClick ? 0 : GetMenu (EV_DECOR3);

    if (!m_pDecor->GetObject (cel, channel, icon))
      return false;

    if (bMix && tableHome[menu * 10 + m_lastHome[menu]] == icon)
    {
      m_lastHome[menu]++;
      if (tableHome[menu * 10 + m_lastHome[menu]] == 0)
        m_lastHome[menu] = 0;
    }

    if (mod & KMOD_CTRL) // touche Ctrl enfoncée ?
    {
      WaitMouse (true);
      m_pDecor->ArrangeFill (
        cel, CHOBJECT, tableHome[menu * 10 + m_lastHome[menu]], false);
      WaitMouse (false);
    }
    else
    {
      icon = tableHome[menu * 10 + m_lastHome[menu]];
      BuildFloor (cel, icon); // met un sol si nécessaire
      m_pDecor->PutObject (cel, CHOBJECT, icon);
      m_pDecor->ArrangeObject (cel);
    }
  }

  if (GetState (EV_DECOR4) == 1 || isRightClick) // pose d'un blupi
  {
    cel  = m_pDecor->ConvPosToCel (pos);
    menu = isRightClick ? 0 : GetMenu (EV_DECOR4);

    if (menu == 0) // supprime ?
      m_pDecor->BlupiDelete (cel);
    if (menu == 1) // ajoute blupi-fatigué ?
      m_pDecor->BlupiCreate (cel, ACTION_STOP, DIRECT_S, 0, MAXENERGY / 4);
    if (menu == 2) // ajoute blupi-énergique ?
      m_pDecor->BlupiCreate (cel, ACTION_STOP, DIRECT_S, 0, MAXENERGY);
    if (menu == 3) // ajoute assistant ?
      m_pDecor->BlupiCreate (cel, ACTION_STOP, DIRECT_S, 8, MAXENERGY);
    if (menu == 4) // ajoute araignée ?
      m_pDecor->BlupiCreate (cel, ACTION_STOP, DIRECT_S, 1, MAXENERGY);
    if (menu == 5) // ajoute virus ?
      m_pDecor->BlupiCreate (cel, ACTION_STOP, DIRECT_S, 2, MAXENERGY);
    if (menu == 6) // ajoute tracks ?
      m_pDecor->BlupiCreate (cel, ACTION_STOP, DIRECT_S, 3, MAXENERGY);
    if (menu == 7) // ajoute bombe ?
      m_pDecor->BlupiCreate (cel, ACTION_STOP, DIRECT_S, 5, MAXENERGY);
    if (menu == 8) // ajoute électro ?
      m_pDecor->BlupiCreate (cel, ACTION_STOP, DIRECT_S, 7, MAXENERGY);
    if (menu == 9) // ajoute robot ?
      m_pDecor->BlupiCreate (cel, ACTION_STOP, DIRECT_S, 4, MAXENERGY);
  }

  if (GetState (EV_DECOR5) == 1 || isRightClick) // pose d'une cata
  {
    cel  = m_pDecor->ConvPosToCel2 (pos);
    menu = isRightClick ? 0 : GetMenu (EV_DECOR5);

    if (menu == 0) // supprime ?
      m_pDecor->SetFire (cel, false);

    if (menu == 1 && (g_restoreBugs || m_pDecor->CanBurn (cel))) // ajoute ?
      m_pDecor->SetFire (cel, true);
  }

  m_pDecor->ArrangeBlupi (); // supprime les blupi bloqués

  return true;
}

// Modifie le décor lorsque la souris est déplacée.

bool
CEvent::BuildMove (Point pos, Uint16 mod, const SDL_Event & event)
{
  if (event.motion.state & SDL_BUTTON (SDL_BUTTON_LEFT)) // bouton souris pressé
    BuildDown (pos, mod, nullptr, false);

  if (GetState (EV_DECOR4) == 1) // pose d'un blupi
    m_pDecor->CelHili (pos, 1);
  else
    m_pDecor->CelHili (pos, 2);

  return true;
}

// Démarre un film non interractif.

bool
CEvent::StartMovie (const std::string & pFilename)
{
  if (!m_pMovie->GetEnable ())
    return false;
  if (!m_bMovie)
    return false;

  std::string absolute;
  if (!FileExists (pFilename, absolute))
    return false;

  HideMouse (true);
  m_pSound->StopMusic ();

  if (!m_pMovie->Play (pFilename))
    return false;

  m_bRunMovie = true;
  return true;
}

// Stoppe un film non interractif.

void
CEvent::StopMovie ()
{
  m_pMovie->Stop ();
  ChangePhase (m_phase);
  m_bRunMovie = false;
}

// Indique s'il y a un film en cours.

bool
CEvent::IsMovie ()
{
  return m_bRunMovie;
}

// Lit une partie (user000.blp).

void
CEvent::Read (Sint32 message)
{
  Sint32 world, time, total;

  m_pDecor->Read (message - EV_READ0, true, world, time, total);
  m_pDecor->SetTime (time);
  m_pDecor->SetTotalTime (total);

  if (world >= 200)
  {
    m_private  = world - 200;
    m_bSchool  = false;
    m_bPrivate = true;
  }
  else if (world >= 100)
  {
    m_mission  = world - 100;
    m_bSchool  = false;
    m_bPrivate = false;
  }
  else
  {
    m_exercice = world;
    m_bSchool  = true;
    m_bPrivate = false;
  }
}

// Ecrit une partie (user000.blp).

void
CEvent::Write (Sint32 message)
{
  Sint32 time, total;

  time  = m_pDecor->GetTime ();
  total = m_pDecor->GetTotalTime ();

  m_pDecor->Write (message - EV_WRITE0, true, GetPhysicalWorld (), time, total);
}

// Initialise le libellé d'une mission privée.

void
CEvent::PrivateLibelle ()
{
  Sint32       i, nb, h1, h2;
  Term         term;
  char         string[100];
  char         buffer[100];
  const char * text = nullptr;

  snprintf (m_libelle, sizeof (m_libelle), "%s", gettext ("1|Goal :"));

  memcpy (&term, m_pDecor->GetTerminated (), sizeof (Term));
  nb = 0;
  for (i = 0; i < 2; i++) // 2 objectifs au maximum !
  {
    text = nullptr;

    if (term.bKillRobots)
    {
      term.bKillRobots = false;
      text             = gettext ("1|Kill all\n1|enemies !");
    }
    else if (term.bHachBlupi)
    {
      term.bHachBlupi = false;
      text            = gettext ("1|Go on striped\n1|paving stones.");
    }
    else if (term.bHachPlanche)
    {
      term.bHachPlanche = false;
      text = gettext ("1|Drop planks on striped \n1|paving stones.");
    }
    else if (term.bHachTomate)
    {
      term.bHachTomate = false;
      text = gettext ("1|Drop tomatoes on striped \n1|paving stones.");
    }
    else if (term.bHachMetal)
    {
      term.bHachMetal = false;
      text = gettext ("1|Drop platinium on striped \n1|paving stones.");
    }
    else if (term.bHachRobot)
    {
      term.bHachRobot = false;
      text = gettext ("1|The robot must reach\n1|the striped paving stones.");
    }
    else if (term.bHomeBlupi)
    {
      term.bHomeBlupi = false;
      text            = gettext ("1|Each Blupi in\n1|his house.");
    }
    else if (term.bStopFire)
    {
      term.bStopFire = false;
      text           = gettext ("1|Resist until\n1|fire extinction ...");
    }

    if (!text)
      break;

    strcat (m_libelle, "\n1|\n");
    strcat (m_libelle, text);
    nb++;
  }

  if (nb == 0 || term.nbMaxBlupi > 1)
  {
    snprintf (
      buffer, sizeof (buffer), "%s",
      gettext ("1|The Blupi population must\n1|be of at least %d Blupi."));
    snprintf (string, sizeof (string), buffer, term.nbMaxBlupi);
    strcat (m_libelle, "\n1|\n");
    strcat (m_libelle, string);
  }

  h1 = GetTextHeight (m_libelle, FONTLITTLE, 1);
  h2 = GetTextHeight (m_libelle, FONTLITTLE, 2);
  if (h2 > h1)
    h1 = h2;
  m_pDecor->SetInfoHeight (POSDRAWY + h1 + 10);
}

// Lit le libellé d'un monde.

bool
CEvent::ReadLibelle (Sint32 world, bool bSchool, bool bHelp)
{
  FILE * file    = nullptr;
  char * pBuffer = nullptr;
  char * pText;
  char * pDest;
  char   indic;
  Sint32 h1, h2;
  size_t nb;

  if (bSchool)
    indic = '$';
  else
    indic = '#';
  if (bHelp)
    indic = '@';

  auto stories = GetBaseDir () + "data/" + GetLocale () + "/stories.blp";

  pBuffer = (char *) malloc (sizeof (char) * 50000);
  if (pBuffer == nullptr)
    goto error;

  memset (pBuffer, 0, sizeof (char) * 50000);

  file = fopen (stories.c_str (), "rb");
  if (file == nullptr)
  {
    /* Try with the fallback locale */
    stories = GetBaseDir () + "data/en/stories.blp";
    file    = fopen (stories.c_str (), "rb");
    if (!file)
      goto error;
  }

  nb          = fread (pBuffer, sizeof (char), 50000 - 1, file);
  pBuffer[nb] = 0;

  pText = pBuffer;
  while (world >= 0)
  {
    while (*pText != 0 && *pText != indic)
      pText++;
    if (*pText == indic)
      pText++;
    world--;
  }
  while (*pText != 0 && *pText != '\n')
    pText++;
  if (*pText == '\n')
    pText++;
  pDest = m_libelle;
  while (*pText != 0 && *pText != indic && *pText != '$' && *pText != '#' &&
         *pText != '@')
    *pDest++ = *pText++;
  *pDest = 0;

  h1 = GetTextHeight (m_libelle, FONTLITTLE, 1);
  h2 = GetTextHeight (m_libelle, FONTLITTLE, 2);
  if (h2 > h1)
    h1 = h2;
  m_pDecor->SetInfoHeight (POSDRAWY + h1 + 10);

  free (pBuffer);
  fclose (file);
  return true;

error:
  if (pBuffer != nullptr)
    free (pBuffer);
  if (file != nullptr)
    fclose (file);
  return false;
}

// Sauve les informations sur disque.

bool
CEvent::WriteInfo ()
{
  std::string filename;
  FILE *      file = nullptr;
  DescInfo    info = {0};
  size_t      nb;

  filename = "data/info.blp";
  AddUserPath (filename);

  file = fopen (filename.c_str (), "wb");
  if (file == nullptr)
    goto error;

  info.majRev       = 1;
  info.minRev       = 3;
  info.prive        = m_private;
  info.exercice     = m_exercice;
  info.mission      = m_mission;
  info.maxMission   = m_maxMission;
  info.speed        = m_speed;
  info.bMovie       = m_bMovie;
  info.scrollSpeed  = m_scrollSpeed;
  info.bAccessBuild = m_bAccessBuild;

  info.skill = m_pDecor->GetSkill ();

  info.audioVolume = m_pSound->GetAudioVolume ();
  info.midiVolume  = m_pSound->GetMidiVolume ();

  /* Global settings */
  info.language = static_cast<Sint16> (
    this->GetLanguage () != this->GetStartLanguage () ? this->GetLanguage ()
                                                      : Language::undef);
  info.musicMidi     = g_restoreMidi;
  info.fullScreen    = g_bFullScreen;
  info.zoom          = g_zoom;
  info.renderQuality = g_renderQuality;

  nb = fwrite (&info, sizeof (info), 1, file);
  if (nb < 1)
    goto error;

  fclose (file);
  return true;

error:
  if (file != nullptr)
    fclose (file);
  return false;
}

// Lit les informations sur disque.

bool
CEvent::ReadInfo ()
{
  std::string filename;
  FILE *      file = nullptr;
  DescInfo    info;
  size_t      nb;

  filename = "data/info.blp";
  AddUserPath (filename);

  file = fopen (filename.c_str (), "rb");
  if (file == nullptr)
    goto error;

  SDL_memset (&info, 0, sizeof (info));

  nb = fread (&info, sizeof (DescInfo), 1, file);
  if (nb < 1)
    goto error;

  m_private      = info.prive;
  m_exercice     = info.exercice;
  m_mission      = info.mission;
  m_maxMission   = info.maxMission;
  m_speed        = info.speed;
  m_bMovie       = !!info.bMovie;
  m_scrollSpeed  = info.scrollSpeed;
  m_bAccessBuild = !!info.bAccessBuild;

  m_pDecor->SetSkill (info.skill);

  m_pSound->SetAudioVolume (info.audioVolume);
  m_pSound->SetMidiVolume (info.midiVolume);

  if ((info.majRev == 1 && info.minRev >= 1) || info.majRev >= 2)
  {
    if (info.language >= static_cast<int> (Language::end))
      info.language = 0;
    this->SetLanguage (static_cast<Language> (info.language));
  }

  if (((info.majRev == 1 && info.minRev >= 2) || info.majRev >= 2))
  {
    if (!(g_settingsOverload & SETTING_MIDI))
      g_restoreMidi = !!info.musicMidi;
    if (!(g_settingsOverload & SETTING_FULLSCREEN))
      g_bFullScreen = !!info.fullScreen;
    if (!(g_settingsOverload & SETTING_ZOOM))
      g_zoom = info.zoom;

    /* Prefer the desktop fullscreen mode by default. */
    if (!(g_settingsOverload & SETTING_LEGACY) && g_bFullScreen && g_zoom == 2)
      g_zoom = 1;
  }

  if (((info.majRev == 1 && info.minRev >= 3) || info.majRev >= 2))
  {
    if (!(g_settingsOverload & SETTING_RENDERQUALITY))
      g_renderQuality = !!info.renderQuality;
  }

  fclose (file);
  return true;

error:
  if (file != nullptr)
    fclose (file);
  return false;
}

// Modifie la vitesse du jeu.

void
CEvent::SetSpeed (Sint32 speed)
{
  Sint32 max;

  if (m_bSpeed)
    max = 8;
  else
    max = 2;

  if (speed > max)
    speed = max;

  m_speed = speed;
}

Sint32
CEvent::GetSpeed ()
{
  return m_speed;
}

bool
CEvent::GetPause ()
{
  return m_bPause;
}

// Début de l'enregistrement d'une démo.

void
CEvent::DemoRecStart ()
{
  m_pDemoSDLBuffer.clear ();
  m_demoTime  = 0;
  m_demoIndex = 0;
  m_bDemoRec  = true;
  m_bDemoPlay = false;

  InitRandom ();
  m_pDecor->SetTime (0);
  m_speed = 1;

  if (this->m_scrollSpeedPrev == -1)
    this->m_scrollSpeedPrev = this->m_scrollSpeed;
  this->m_scrollSpeed = 3;

  m_bStartRecording = true;
}

// Fin de l'enregistrement d'une démo.
// Sauve le fichier sur disque.

void
CEvent::DemoRecStop ()
{
  FILE *     file = nullptr;
  DemoHeader header;

  if (m_bDemoPlay || !m_bDemoRec)
    return;

  std::time_t t = std::time (nullptr);
  std::localtime (&t);
  std::string demoPath = "demo/demo." + std::to_string (t) + ".blp";
  AddUserPath (demoPath);

  unlink (demoPath.c_str ());
  file = fopen (demoPath.c_str (), "wb");
  if (file)
  {
    memset (&header, 0, sizeof (DemoHeader));
    header.majRev   = 2;
    header.minRev   = 0;
    header.bSchool  = m_bSchool;
    header.bPrivate = m_bPrivate;
    header.world    = GetPhysicalWorld ();
    header.skill    = m_pDecor->GetSkill ();

    fwrite (&header, sizeof (DemoHeader), 1, file);
    for (const auto buffer : m_pDemoSDLBuffer)
      fwrite (&buffer, sizeof (DemoSDLEvent), 1, file);
    fclose (file);
  }

  m_pDemoSDLBuffer.clear ();
  m_bDemoRec        = false;
  m_demoTime        = 0;
  m_bStartRecording = false;

  if (this->m_scrollSpeedPrev >= 0)
  {
    this->m_scrollSpeed     = this->m_scrollSpeedPrev;
    this->m_scrollSpeedPrev = -1;
  }
}

// Début de la reproduction d'une démo.
// Lit le fichier sur disque.

bool
CEvent::DemoPlayStart (const std::string * demoFile)
{
  std::string filename;
  FILE *      file = nullptr;
  DemoHeader  header;
  Sint32      world, time, total;
  size_t      nb;

  filename =
    demoFile
      ? *demoFile
      : string_format (GetBaseDir () + "data/demo%.3d.blp", m_demoNumber);
  file = fopen (filename.c_str (), "rb");
  if (file == nullptr)
  {
    DemoPlayStop ();
    return false;
  }

  nb = fread (&header, sizeof (DemoHeader), 1, file);
  if (nb < 1)
  {
    fclose (file);
    DemoPlayStop ();
    return false;
  }

  if (header.majRev == 1)
  {
    m_pDemoBuffer = (DemoEvent *) malloc (MAXDEMO * sizeof (DemoEvent));
    if (m_pDemoBuffer == nullptr)
    {
      fclose (file);
      DemoPlayStop ();
      return false;
    }
    memset (m_pDemoBuffer, 0, MAXDEMO * sizeof (DemoEvent));
  }

  m_bSchool  = !!header.bSchool;
  m_bPrivate = !!header.bPrivate;
  m_pDecor->SetSkill (header.skill);

  if (header.majRev == 1)
    m_demoEnd = fread (m_pDemoBuffer, sizeof (DemoEvent), MAXDEMO, file);
  else if (header.majRev == 2)
  {
    DemoSDLEvent demoEvent;

    for (;;)
    {
      auto res = fread (&demoEvent, sizeof (DemoSDLEvent), 1, file);
      if (res != 1)
        break;

      m_pDemoSDLBuffer.push_back (demoEvent);
    }

    m_demoEnd = m_pDemoSDLBuffer.size ();
  }
  fclose (file);

  m_demoTime  = 0;
  m_demoIndex = 0;
  m_bDemoPlay = true;
  m_bDemoRec  = false;

  if (!m_pDecor->Read (header.world, false, world, time, total))
  {
    DemoPlayStop ();
    return false;
  }

  if (this->m_scrollSpeedPrev == -1)
    this->m_scrollSpeedPrev = this->m_scrollSpeed;
  this->m_scrollSpeed = 3;

  ChangePhase (EV_PHASE_PLAY);
  InitRandom ();
  m_pDecor->SetTime (0);
  m_speed = 1;

  return true;
}

// Fin de la reproduction d'une démo.

void
CEvent::DemoPlayStop ()
{
  if (m_pDemoBuffer != nullptr)
  {
    free (m_pDemoBuffer);
    m_pDemoBuffer = nullptr;
  }

  m_pDemoSDLBuffer.clear ();

  m_bDemoPlay = false;
  m_bDemoRec  = false;
  m_demoTime  = 0;

  if (this->m_scrollSpeedPrev >= 0)
  {
    this->m_scrollSpeed     = this->m_scrollSpeedPrev;
    this->m_scrollSpeedPrev = -1;
  }

  ChangePhase (EV_PHASE_INIT);
}

void
CEvent::WinToSDLEvent (
  Uint32 msg, WParam wParam, LParam lParam, SDL_Event & event)
{
#define GET_X_LParam(lp) ((Sint32) (Sint16) LOWORD (lp))
#define GET_Y_LParam(lp) ((Sint32) (Sint16) HIWORD (lp))

  // clang-format off
  static const std::unordered_map<Uint32, SDL_Keysym> keycodes = {
    { '0',      { SDL_SCANCODE_0,     SDLK_0,     0, 0 } },
    { '1',      { SDL_SCANCODE_1,     SDLK_1,     0, 0 } },
    { '2',      { SDL_SCANCODE_2,     SDLK_2,     0, 0 } },
    { '3',      { SDL_SCANCODE_3,     SDLK_3,     0, 0 } },
    { '4',      { SDL_SCANCODE_4,     SDLK_4,     0, 0 } },
    { '5',      { SDL_SCANCODE_5,     SDLK_5,     0, 0 } },
    { '6',      { SDL_SCANCODE_6,     SDLK_6,     0, 0 } },
    { '7',      { SDL_SCANCODE_7,     SDLK_7,     0, 0 } },
    { '8',      { SDL_SCANCODE_8,     SDLK_8,     0, 0 } },
    { '9',      { SDL_SCANCODE_9,     SDLK_9,     0, 0 } },
    { 'A',      { SDL_SCANCODE_A,     SDLK_a,     0, 0 } },
    { 'B',      { SDL_SCANCODE_B,     SDLK_b,     0, 0 } },
    { 'C',      { SDL_SCANCODE_C,     SDLK_c,     0, 0 } },
    { 'D',      { SDL_SCANCODE_D,     SDLK_d,     0, 0 } },
    { 'E',      { SDL_SCANCODE_E,     SDLK_e,     0, 0 } },
    { 'F',      { SDL_SCANCODE_F,     SDLK_f,     0, 0 } },
    { 'G',      { SDL_SCANCODE_G,     SDLK_g,     0, 0 } },
    { 'H',      { SDL_SCANCODE_H,     SDLK_h,     0, 0 } },
    { 'I',      { SDL_SCANCODE_I,     SDLK_i,     0, 0 } },
    { 'J',      { SDL_SCANCODE_J,     SDLK_j,     0, 0 } },
    { 'K',      { SDL_SCANCODE_K,     SDLK_k,     0, 0 } },
    { 'L',      { SDL_SCANCODE_L,     SDLK_l,     0, 0 } },
    { 'M',      { SDL_SCANCODE_M,     SDLK_m,     0, 0 } },
    { 'N',      { SDL_SCANCODE_N,     SDLK_n,     0, 0 } },
    { 'O',      { SDL_SCANCODE_O,     SDLK_o,     0, 0 } },
    { 'P',      { SDL_SCANCODE_P,     SDLK_p,     0, 0 } },
    { 'Q',      { SDL_SCANCODE_Q,     SDLK_q,     0, 0 } },
    { 'R',      { SDL_SCANCODE_R,     SDLK_r,     0, 0 } },
    { 'S',      { SDL_SCANCODE_S,     SDLK_0,     0, 0 } },
    { 'T',      { SDL_SCANCODE_T,     SDLK_t,     0, 0 } },
    { 'U',      { SDL_SCANCODE_U,     SDLK_u,     0, 0 } },
    { 'V',      { SDL_SCANCODE_V,     SDLK_v,     0, 0 } },
    { 'W',      { SDL_SCANCODE_W,     SDLK_w,     0, 0 } },
    { 'X',      { SDL_SCANCODE_X,     SDLK_x,     0, 0 } },
    { 'Y',      { SDL_SCANCODE_Y,     SDLK_y,     0, 0 } },
    { 'Z',      { SDL_SCANCODE_Z,     SDLK_z,     0, 0 } },
    { VK_LEFT,  { SDL_SCANCODE_LEFT,  SDLK_LEFT,  0, 0 } },
    { VK_UP,    { SDL_SCANCODE_UP,    SDLK_UP,    0, 0 } },
    { VK_RIGHT, { SDL_SCANCODE_RIGHT, SDLK_RIGHT, 0, 0 } },
    { VK_DOWN,  { SDL_SCANCODE_DOWN,  SDLK_DOWN,  0, 0 } },
    { VK_END,   { SDL_SCANCODE_END,   SDLK_END,   0, 0 } },
  };
  // clang-format on

  try
  {
    switch (msg)
    {
    case EV_KEYUP:
    case EV_KEYDOWN:
      event.type       = msg == EV_KEYDOWN ? SDL_KEYDOWN : SDL_KEYUP;
      event.key.keysym = keycodes.at (wParam);
      // TODO: lParam
      break;

    case EV_LBUTTONUP:
    case EV_LBUTTONDOWN:
      event.type =
        msg == EV_LBUTTONDOWN ? SDL_MOUSEBUTTONDOWN : SDL_MOUSEBUTTONUP;
      event.button.button = SDL_BUTTON_LEFT;
      // TODO: wParam CTRL or SHIFT
      event.button.x = GET_X_LParam (lParam);
      event.button.y = GET_Y_LParam (lParam);
      break;

    case EV_RBUTTONUP:
    case EV_RBUTTONDOWN:
      event.type =
        msg == EV_RBUTTONDOWN ? SDL_MOUSEBUTTONDOWN : SDL_MOUSEBUTTONUP;
      event.button.button = SDL_BUTTON_RIGHT;
      // TODO: wParam CTRL or SHIFT
      event.button.x = GET_X_LParam (lParam);
      event.button.y = GET_Y_LParam (lParam);
      break;

    case EV_MOUSEMOVE:
      event.type = SDL_MOUSEMOTION;
      // TODO: wParam CTRL or SHIFT
      event.motion.x = GET_X_LParam (lParam);
      event.motion.y = GET_Y_LParam (lParam);
      break;
    }
  }
  catch (...)
  {
    SDL_LogError (SDL_LOG_CATEGORY_APPLICATION, "unsupported keycode");
  }
}

// Avance l'index d'enregistrement ou de reproduction.

void
CEvent::DemoStep ()
{
  Uint32 time    = 0;
  Uint32 message = 0;
  WParam wParam  = 0;
  LParam lParam  = 0;

  if (m_phase == EV_PHASE_INIT)
  {
    if (!g_playRecord.empty ())
    {
      m_demoNumber = -1;
      DemoPlayStart (&g_playRecord);
      g_playRecord = "";
    }
    else if (m_demoTime > DEF_TIME_DEMO) // ~30 secondes écoulées ?
    {
      m_demoNumber = 0;
      DemoPlayStart (); // démarre une démo automatique
    }
  }

  if (m_bDemoPlay)
  {
    while (true)
    {
      SDL_Event event = {0};

      if (m_pDemoBuffer) // Old Win32 events format
      {
        time    = m_pDemoBuffer[m_demoIndex].time;
        message = m_pDemoBuffer[m_demoIndex].message;
        wParam  = m_pDemoBuffer[m_demoIndex].wParam;
        lParam  = m_pDemoBuffer[m_demoIndex].lParam;

        if (IsRightReading ())
          lParam =
            (lParam & 0xFFFF0000) | ((lParam & 0xFFFF) - POSDRAWX_ + POSDRAWX);
      }
      else // New SDL events format
      {
        time       = m_pDemoSDLBuffer[m_demoIndex].time;
        event.type = m_pDemoSDLBuffer[m_demoIndex].type;
        event.key.keysym.scancode =
          static_cast<SDL_Scancode> (m_pDemoSDLBuffer[m_demoIndex].scancode);
        event.key.keysym.sym = m_pDemoSDLBuffer[m_demoIndex].sym;
        if (event.type == SDL_MOUSEMOTION)
        {
          event.motion.x = m_pDemoSDLBuffer[m_demoIndex].x;
          event.motion.y = m_pDemoSDLBuffer[m_demoIndex].y;

          if (IsRightReading ())
            event.motion.x -= POSDRAWX_ + POSDRAWX;
        }
        else if (event.type != SDL_KEYUP && event.type != SDL_KEYDOWN)
        {
          event.button.button = m_pDemoSDLBuffer[m_demoIndex].button;
          event.button.x      = m_pDemoSDLBuffer[m_demoIndex].x;
          event.button.y      = m_pDemoSDLBuffer[m_demoIndex].y;

          if (IsRightReading ())
            event.button.x -= POSDRAWX_ + POSDRAWX;
        }
      }

      if (time > m_demoTime)
        break;

      m_demoIndex++;

      if (message == EV_MOUSEMOVE || event.type == SDL_MOUSEMOTION)
      {
        Point pos;

        if (m_pDemoBuffer)
          pos = ConvLongToPos (lParam);
        else
        {
          pos.x = event.motion.x;
          pos.y = event.motion.y;
        }

        this->m_pPixmap->FromGameToDisplay (pos.x, pos.y);
        SDL_WarpMouseInWindow (g_window, pos.x, pos.y);
      }

      if (m_pDemoBuffer)
        CEvent::WinToSDLEvent (message, wParam, lParam, event);

      TreatEventBase (event);

      if (m_demoIndex >= m_demoEnd && m_demoNumber >= 0)
      {
        m_demoNumber++;        // démo suivante
        if (!DemoPlayStart ()) // démarre la démo suivante
        {
          m_demoNumber = 0; // première démo
          DemoPlayStart (); // démarre la démo
        }
        return;
      }
    }
  }

  m_demoTime++;
}

/**
 * \brief Store an event for the demos
 */
void
CEvent::DemoRecEvent (const SDL_Event & event)
{
  if (!m_bDemoRec)
    return;

  DemoSDLEvent demoEvent = {0};

  switch (event.type)
  {
  case SDL_KEYUP:
  case SDL_KEYDOWN:
    demoEvent.type     = event.type;
    demoEvent.time     = m_demoTime;
    demoEvent.scancode = event.key.keysym.scancode;
    demoEvent.sym      = event.key.keysym.sym;
    break;

  case SDL_MOUSEBUTTONUP:
  case SDL_MOUSEBUTTONDOWN:
    demoEvent.type   = event.type;
    demoEvent.time   = m_demoTime;
    demoEvent.button = event.button.button;
    demoEvent.x      = event.button.x;
    demoEvent.y      = event.button.y;
    this->m_pPixmap->FromDisplayToGame (demoEvent.x, demoEvent.y);
    break;

  case SDL_MOUSEMOTION:
    demoEvent.type = event.type;
    demoEvent.time = m_demoTime;
    demoEvent.x    = event.motion.x;
    demoEvent.y    = event.motion.y;
    this->m_pPixmap->FromDisplayToGame (demoEvent.x, demoEvent.y);
    break;

  default:
    return;
  }

  m_pDemoSDLBuffer.push_back (demoEvent);

  m_demoIndex = m_pDemoSDLBuffer.size ();
}

// Retourne la dernière position de la souris.

Point
CEvent::GetLastMousePos ()
{
  return m_oldMousePos;
}

// Traitement d'un événement.

bool
CEvent::TreatEvent (const SDL_Event & event)
{
  if (m_bDemoPlay)
  {
    if (
      event.type == SDL_KEYDOWN || event.type == SDL_KEYUP ||
      event.type == SDL_MOUSEBUTTONUP) // is the user clicking?
    {
      DemoPlayStop ();
      return true;
    }

    if (event.type == SDL_MOUSEMOTION) // is the user moving?
      return true;
  }

  return TreatEventBase (event);
}

// Traitement d'un événement.

bool
CEvent::TreatEventBase (const SDL_Event & event)
{
  Point  pos;
  Sint32 i;
  Sounds sound;
  char   c;
  bool   bEnable;

  DemoRecEvent (event);

  switch (event.type)
  {
  case SDL_KEYDOWN:
    if (event.key.keysym.sym >= SDLK_a && event.key.keysym.sym <= SDLK_z)
    {
      if (m_posCheat == 0) // première lettre ?
      {
        m_rankCheat = -1;
        for (i = 0; i < 9; i++)
        {
          if ((char) event.key.keysym.sym == cheat_code[i][0])
          {
            m_rankCheat = i;
            break;
          }
        }
      }
      if (m_rankCheat != -1)
      {
        c = cheat_code[m_rankCheat][m_posCheat];
        if (m_posCheat != 0 && m_rankCheat == 8)
          c++; // CONSTRUIRE ?
        if ((char) event.key.keysym.sym == c)
        {
          m_posCheat++;
          if (cheat_code[m_rankCheat][m_posCheat] == 0)
          {
            bEnable = true;
            if (m_phase == EV_PHASE_PLAY)
            {
              if (m_rankCheat == 0) // vision ?
                m_pDecor->EnableFog (false);
              else if (
                m_rankCheat == 1 || // power ?
                m_rankCheat == 2)   // lonesome ?
                m_pDecor->BlupiCheat (m_rankCheat);
            }

            switch (m_rankCheat)
            {
            case 3: // allmissions ?
            {
              m_bAllMissions = !m_bAllMissions;
              bEnable        = m_bAllMissions;
              break;
            }
            case 4: // quick ?
            {
              m_bSpeed = !m_bSpeed;
              bEnable  = m_bSpeed;
              break;
            }
            case 5: // helpme ?
            {
              m_bHelp = !m_bHelp;
              bEnable = m_bHelp;
              break;
            }
            case 6: // invincible ?
            {
              m_pDecor->SetInvincible (!m_pDecor->GetInvincible ());
              bEnable = m_pDecor->GetInvincible ();
              break;
            }
            case 7: // superblupi ?
            {
              m_pDecor->SetSuper (!m_pDecor->GetSuper ());
              bEnable = m_pDecor->GetSuper ();
              break;
            }
            case 8: // construire ?
            {
              m_bAccessBuild = !m_bAccessBuild;
              bEnable        = m_bAccessBuild;
              break;
            }
            }

            if (m_phase != EV_PHASE_PLAY)
              ChangePhase (m_phase);

            pos.x = LXIMAGE () / 2;
            pos.y = LYIMAGE () / 2;
            if (bEnable)
              m_pSound->PlayImage (SOUND_GOAL, pos);
            else
              m_pSound->PlayImage (SOUND_BOING, pos);

            m_rankCheat = -1;
            m_posCheat  = 0;
          }
          return true;
        }
      }
    }
    m_rankCheat = -1;
    m_posCheat  = 0;

    if (m_phase == EV_PHASE_INTRO1)
    {
      ChangePhase (EV_PHASE_INIT);
      return true;
    }

    if (m_phase == EV_PHASE_BYE)
    {
      SDL_Event ev;
      ev.type = SDL_QUIT;
      SDL_PushEvent (&ev);
    }

    switch (event.key.keysym.sym)
    {
    case SDLK_END:
      DemoRecStop ();
      return true;
    case SDLK_ESCAPE:
      if (m_bRunMovie)
      {
        StopMovie ();
        return true;
      }

      switch (m_phase)
      {
      case EV_PHASE_PLAY:
      case EV_PHASE_SETUPp:
      case EV_PHASE_WRITE:
      case EV_PHASE_WRITEp:
      case EV_PHASE_HELP:
        ChangePhase (EV_PHASE_STOP);
        return true;

      case EV_PHASE_SETUP:
      case EV_PHASE_READ:
        ChangePhase (
          this->m_pDecor->GetTime () ? EV_PHASE_STOP : EV_PHASE_INFO);
        return true;

      case EV_PHASE_STOP:
        ChangePhase (EV_PHASE_PLAY);
        return true;

      case EV_PHASE_LOST:
      case EV_PHASE_BUILD:
        ChangePhase (EV_PHASE_INFO);
        return true;

      case EV_PHASE_INFO:
      case EV_PHASE_SETTINGS:
        ChangePhase (EV_PHASE_INIT);
        return true;

      case EV_PHASE_BUTTON:
      case EV_PHASE_TERM:
      case EV_PHASE_MUSIC:
      case EV_PHASE_REGION:
        ChangePhase (EV_PHASE_BUILD);
        return true;

      case EV_PHASE_INIT:
        ChangePhase (EV_PHASE_BYE);
        return true;

      case EV_PHASE_BYE:
      {
        SDL_Event ev;
        ev.type = SDL_QUIT;
        SDL_PushEvent (&ev);
        break;
      }
      }
      return true;
    case SDLK_RETURN:
      switch (m_phase)
      {
      case EV_PHASE_SETTINGS:
        ChangePhase (EV_PHASE_INIT);
        return true;

      case EV_PHASE_PLAY:
      case EV_PHASE_WRITE:
        ChangePhase (EV_PHASE_STOP);
        return true;

      case EV_PHASE_SETUP:
      case EV_PHASE_READ:
        ChangePhase (
          this->m_pDecor->GetTime () ? EV_PHASE_STOP : EV_PHASE_INFO);
        return true;

      case EV_PHASE_INIT:
      case EV_PHASE_LOST:
      case EV_PHASE_BUILD:
        ChangePhase (EV_PHASE_INFO);
        return true;

      case EV_PHASE_INFO:
      case EV_PHASE_STOP:
      case EV_PHASE_HELP:
      case EV_PHASE_SETUPp:
      case EV_PHASE_WRITEp:
        ChangePhase (EV_PHASE_PLAY);
        return true;

      case EV_PHASE_BUTTON:
      case EV_PHASE_TERM:
      case EV_PHASE_MUSIC:
      case EV_PHASE_REGION:
        ChangePhase (EV_PHASE_BUILD);
        return true;
      }

      return true;
    case SDLK_LEFT:
    case SDLK_RIGHT:
    case SDLK_UP:
    case SDLK_DOWN:
    {
      if (m_phase != EV_PHASE_PLAY && m_phase != EV_PHASE_BUILD)
        return true;

      bool          left, right, up, down;
      const Uint8 * state = SDL_GetKeyboardState (nullptr);

      this->shiftDirection = 0;

      left = event.key.keysym.sym == SDLK_LEFT ||
             (!m_bDemoRec && state[SDL_SCANCODE_LEFT]);
      right = event.key.keysym.sym == SDLK_RIGHT ||
              (!m_bDemoRec && state[SDL_SCANCODE_RIGHT]);
      up = event.key.keysym.sym == SDLK_UP ||
           (!m_bDemoRec && state[SDL_SCANCODE_UP]);
      down = event.key.keysym.sym == SDLK_DOWN ||
             (!m_bDemoRec && state[SDL_SCANCODE_DOWN]);

      if (left)
        this->shiftDirection |= DIRECTION_LEFT;
      if (right)
        this->shiftDirection |= DIRECTION_RIGHT;
      if (up)
        this->shiftDirection |= DIRECTION_UP;
      if (down)
        this->shiftDirection |= DIRECTION_DOWN;
      return true;
    }
    case SDLK_HOME:
      pos = m_pDecor->GetHome ();
      m_pDecor->SetCorner (pos);
      return true;
    case SDLK_SPACE:
      if (m_bRunMovie)
      {
        StopMovie ();
        return true;
      }
      m_pDecor->FlipOutline ();
      return true;
    case SDLK_PAUSE:
      if (this->m_pDecor->GetSkill () >= 1)
        return true;

      m_bPause = !m_bPause;
      if (m_phase == EV_PHASE_PLAY)
      {
        if (m_bPause)
          m_pSound->SuspendMusic ();
        else
          m_pSound->RestartMusic ();
      }
      return true;

    case SDLK_LSHIFT:
    case SDLK_RSHIFT:
      m_keymod |= KMOD_SHIFT;
      break;

    case SDLK_LCTRL:
    case SDLK_RCTRL:
      m_keymod |= KMOD_CTRL;
      if (m_phase == EV_PHASE_BUILD)
        m_bFillMouse = true;
      else
        m_bFillMouse = false;
      return true;
    case SDLK_F1:
      if (m_phase == EV_PHASE_PLAY)
      {
        // Montre ou cache les infos tout en haut.
        if (m_pDecor->GetInfoMode ())
          sound = SOUND_CLOSE;
        else
          sound = SOUND_OPEN;
        pos.x = LXIMAGE () / 2;
        pos.y = LYIMAGE () / 2;
        m_pSound->PlayImage (sound, pos);
        m_pDecor->SetInfoMode (!m_pDecor->GetInfoMode ());
      }
      return true;
    case SDLK_F3:
      if (
        g_enableRecorder && m_phase == EV_PHASE_PLAY && !m_bDemoPlay &&
        !m_bStartRecording)
        DemoRecStart (); // start recording
      break;
    case SDLK_F4:
      if (
        g_enableRecorder && m_phase == EV_PHASE_PLAY && !m_bDemoPlay &&
        m_bStartRecording)
        DemoRecStop (); // stop recording
      else if (m_phase == EV_PHASE_PLAY && m_bDemoPlay)
      {
        DemoPlayStop ();
        return true;
      }
      break;
    case SDLK_F9:
      if (m_phase == EV_PHASE_PLAY)
        m_pDecor->MemoPos (0, !!(m_keymod & KMOD_CTRL));
      return true;
    case SDLK_F10:
      if (m_phase == EV_PHASE_PLAY)
        m_pDecor->MemoPos (1, !!(m_keymod & KMOD_CTRL));
      return true;
    case SDLK_F11:
      if (m_phase == EV_PHASE_PLAY)
        m_pDecor->MemoPos (2, !!(m_keymod & KMOD_CTRL));
      return true;
    case SDLK_F12:
      if (m_phase == EV_PHASE_PLAY)
        m_pDecor->MemoPos (3, !!(m_keymod & KMOD_CTRL));
      return true;
    }
    break;

  case SDL_KEYUP:
    switch (event.key.keysym.sym)
    {
    case SDLK_LSHIFT:
    case SDLK_RSHIFT:
      m_keymod &= ~KMOD_SHIFT;
      break;

    case SDLK_LCTRL:
    case SDLK_RCTRL:
      m_keymod &= ~KMOD_CTRL;
      m_bFillMouse = false;
      return true;
    }
    break;

  case SDL_MOUSEBUTTONDOWN:
    if (
      event.button.button != SDL_BUTTON_LEFT &&
      event.button.button != SDL_BUTTON_RIGHT)
      break;

    pos.x = event.button.x;
    pos.y = event.button.y;

    if (EventButtons (event, pos))
      return true;
    if (m_phase == EV_PHASE_BUILD)
    {
      if (BuildDown (pos, m_keymod, &event))
        return true;
    }
    if (m_phase == EV_PHASE_PLAY)
    {
      if (PlayDown (pos, event))
        return true;
    }
    break;

  case SDL_MOUSEMOTION:
    pos.x = event.motion.x;
    pos.y = event.motion.y;

    m_oldMousePos = pos;

    if (EventButtons (event, pos))
      return true;
    if (m_phase == EV_PHASE_BUILD)
    {
      if (BuildMove (pos, m_keymod, event))
        return true;
    }
    if (m_phase == EV_PHASE_PLAY)
      return true;
    break;

  case SDL_MOUSEBUTTONUP:
    if (
      event.button.button != SDL_BUTTON_LEFT &&
      event.button.button != SDL_BUTTON_RIGHT)
      break;

    pos.x = event.button.x;
    pos.y = event.button.y;

    if (EventButtons (event, pos))
      return true;
    if (m_phase == EV_PHASE_BUILD)
      return true;
    if (m_phase == EV_PHASE_PLAY)
    {
      if (PlayUp (pos))
        return true;
    }
    if (m_phase == EV_PHASE_BYE)
    {
      SDL_Event ev;
      ev.type = SDL_QUIT;
      SDL_PushEvent (&ev);
    }
    break;

  case SDL_USEREVENT:
    switch (event.user.code)
    {
    case EV_PHASE_DEMO:
      m_demoNumber = 0;
      DemoPlayStart ();
      break;

    case EV_PHASE_SCHOOL:
      m_bSchool  = true;
      m_bPrivate = false;
      if (ChangePhase (EV_PHASE_INFO))
        return true;
      break;

    case EV_PHASE_MISSION:
      m_bSchool  = false;
      m_bPrivate = false;
      if (m_mission == 0) // première mission ?
      {
        if (ChangePhase (EV_PHASE_H0MOVIE))
          return true;
      }
      else
      {
        if (ChangePhase (EV_PHASE_INFO))
          return true;
      }
      break;

    case EV_PHASE_PRIVATE:
      m_bSchool  = false;
      m_bPrivate = true;
      if (ChangePhase (EV_PHASE_INFO))
        return true;
      break;

    case EV_PHASE_INTRO1:
    case EV_PHASE_INIT:
    case EV_PHASE_HISTORY0:
    case EV_PHASE_HISTORY1:
    case EV_PHASE_INFO:
    case EV_PHASE_PLAY:
    case EV_PHASE_READ:
    case EV_PHASE_WRITE:
    case EV_PHASE_WRITEp:
    case EV_PHASE_BUILD:
    case EV_PHASE_BUTTON:
    case EV_PHASE_TERM:
    case EV_PHASE_STOP:
    case EV_PHASE_HELP:
    case EV_PHASE_MUSIC:
    case EV_PHASE_REGION:
    case EV_PHASE_SETTINGS:
    case EV_PHASE_SETUP:
    case EV_PHASE_SETUPp:
    case EV_PHASE_PLAYMOVIE:
    case EV_PHASE_H0MOVIE:
    case EV_PHASE_H1MOVIE:
    case EV_PHASE_H2MOVIE:
    case EV_PHASE_WINMOVIE:
    case EV_PHASE_BYE:
      if (ChangePhase (event.user.code))
        return true;
      break;

    case EV_PHASE_UNDO:
      m_pDecor->UndoBack (); // revient en arrière
      break;

    case EV_PREV:
      m_pDecor->SetInvincible (false);
      m_pDecor->SetSuper (false);
      if (m_bPrivate)
      {
        if (m_private > 0)
        {
          m_private--;
          if (ChangePhase (EV_PHASE_INFO))
            return true;
        }
      }
      else if (m_bSchool)
      {
        if (m_exercice > 0)
        {
          m_exercice--;
          if (ChangePhase (EV_PHASE_INFO))
            return true;
        }
      }
      else
      {
        if (m_mission > 0)
        {
          m_mission--;
          if (ChangePhase (EV_PHASE_INFO))
            return true;
        }
      }
      break;

    case EV_NEXT:
      m_pDecor->SetInvincible (false);
      m_pDecor->SetSuper (false);
      if (m_bPrivate)
      {
        if (m_private < MAX_PRIVATE_MISSIONS - 1)
        {
          m_private++;
          if (ChangePhase (EV_PHASE_INFO))
            return true;
        }
      }
      else if (m_bSchool)
      {
        if (m_exercice < 99)
        {
          m_exercice++;
          if (ChangePhase (EV_PHASE_INFO))
            return true;
        }
      }
      else
      {
        if (m_mission < 99)
        {
          m_mission++;
          if (m_maxMission < m_mission)
            m_maxMission = m_mission;
          if (ChangePhase (EV_PHASE_INFO))
            return true;
        }
      }
      break;

    case EV_DECOR1:
      SetState (EV_DECOR1, 1);
      SetState (EV_DECOR2, 0);
      SetState (EV_DECOR3, 0);
      SetState (EV_DECOR4, 0);
      SetState (EV_DECOR5, 0);
      break;

    case EV_DECOR2:
      SetState (EV_DECOR1, 0);
      SetState (EV_DECOR2, 1);
      SetState (EV_DECOR3, 0);
      SetState (EV_DECOR4, 0);
      SetState (EV_DECOR5, 0);
      break;

    case EV_DECOR3:
      SetState (EV_DECOR1, 0);
      SetState (EV_DECOR2, 0);
      SetState (EV_DECOR3, 1);
      SetState (EV_DECOR4, 0);
      SetState (EV_DECOR5, 0);
      break;

    case EV_DECOR4:
      SetState (EV_DECOR1, 0);
      SetState (EV_DECOR2, 0);
      SetState (EV_DECOR3, 0);
      SetState (EV_DECOR4, 1);
      SetState (EV_DECOR5, 0);
      break;

    case EV_DECOR5:
      SetState (EV_DECOR1, 0);
      SetState (EV_DECOR2, 0);
      SetState (EV_DECOR3, 0);
      SetState (EV_DECOR4, 0);
      SetState (EV_DECOR5, 1);
      break;

    case EV_PHASE_SKILL1:
      m_pDecor->SetSkill (0);
      SetState (EV_PHASE_SKILL1, true);
      SetState (EV_PHASE_SKILL2, false);
      break;
    case EV_PHASE_SKILL2:
      m_pDecor->SetSkill (1);
      SetState (EV_PHASE_SKILL1, false);
      SetState (EV_PHASE_SKILL2, true);
      break;

    case EV_BUTTON0:
    case EV_BUTTON1:
    case EV_BUTTON2:
    case EV_BUTTON3:
    case EV_BUTTON4:
    case EV_BUTTON5:
    case EV_BUTTON6:
    case EV_BUTTON7:
    case EV_BUTTON8:
    case EV_BUTTON9:
    case EV_BUTTON10:
    case EV_BUTTON11:
    case EV_BUTTON12:
    case EV_BUTTON13:
    case EV_BUTTON14:
    case EV_BUTTON15:
    case EV_BUTTON16:
    case EV_BUTTON17:
    case EV_BUTTON18:
    case EV_BUTTON19:
    case EV_BUTTON20:
    case EV_BUTTON21:
    case EV_BUTTON22:
    case EV_BUTTON23:
    case EV_BUTTON24:
    case EV_BUTTON25:
    case EV_BUTTON26:
    case EV_BUTTON27:
    case EV_BUTTON28:
    case EV_BUTTON29:
    case EV_BUTTON30:
    case EV_BUTTON31:
    case EV_BUTTON32:
    case EV_BUTTON33:
    case EV_BUTTON34:
    case EV_BUTTON35:
    case EV_BUTTON36:
    case EV_BUTTON37:
    case EV_BUTTON38:
    case EV_BUTTON39:
      ChangeButtons (event.user.code);
      break;

    case EV_READ0:
    case EV_READ1:
    case EV_READ2:
    case EV_READ3:
    case EV_READ4:
    case EV_READ5:
    case EV_READ6:
    case EV_READ7:
    case EV_READ8:
    case EV_READ9:
      Read (event.user.code);
      ChangePhase (EV_PHASE_PLAY); // joue
      break;

    case EV_SETUP_EXIT:
    case EV_READ_EXIT:
      ChangePhase (this->m_pDecor->GetTime () ? EV_PHASE_STOP : EV_PHASE_INFO);
      break;

    case EV_WRITE0:
    case EV_WRITE1:
    case EV_WRITE2:
    case EV_WRITE3:
    case EV_WRITE4:
    case EV_WRITE5:
    case EV_WRITE6:
    case EV_WRITE7:
    case EV_WRITE8:
    case EV_WRITE9:
      Write (event.user.code);
      if (m_phase == EV_PHASE_WRITEp)
      {
        ChangePhase (EV_PHASE_PLAY); // joue
      }
      else
      {
        ChangePhase (EV_PHASE_STOP); // pause
      }
      break;

    case EV_MOVIE:
      StartMovie ("movie/essai.avi");
      ChangePhase (EV_PHASE_INIT);
      break;
    }
  }

  return false;
}

// Passe les images d'introduction.

void
CEvent::IntroStep ()
{
  m_introTime++;

  if (m_introTime > 20 * 1)
  {
    if (m_phase == EV_PHASE_INTRO1)
    {
      ChangePhase (EV_PHASE_INIT);
      return;
    }
  }
}

void
CEvent::PushUserEvent (Sint32 code, void * data)
{
  SDL_Event event;

  event.type       = SDL_USEREVENT;
  event.user.code  = code;
  event.user.data1 = data;
  event.user.data2 = nullptr;

  SDL_PushEvent (&event);
}
