/*
 * This file is part of the planetblupi source code
 * Copyright (C) 1997, Daniel Roux & EPSITEC SA
 * Copyright (C) 2017, 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 <unordered_map>

#include "action.h"
#include "decgoal.h"
#include "decor.h"
#include "def.h"
#include "gettext.h"
#include "misc.h"

// Cette table donne l'action à effectuer pour un bouton
// enfoncé.
const Sint16 table_actions[] = {
  EV_ACTION_GO,       EV_ACTION_STOP,    EV_ACTION_EAT,       EV_ACTION_CARRY,
  EV_ACTION_DROP,     EV_ACTION_ABAT1,   EV_ACTION_ROC1,      EV_ACTION_CULTIVE,
  EV_ACTION_BUILD1,   EV_ACTION_BUILD2,  EV_ACTION_BUILD3,    EV_ACTION_BUILD4,
  EV_ACTION_BUILD5,   EV_ACTION_BUILD6,  EV_ACTION_WALL,      EV_ACTION_PALIS,
  EV_ACTION_ABAT1,    EV_ACTION_ROC1,    EV_ACTION_BRIDGEE,   EV_ACTION_TOWER,
  EV_ACTION_DRINK,    EV_ACTION_LABO,    EV_ACTION_FLOWER1,   EV_ACTION_FLOWER1,
  EV_ACTION_DYNAMITE, EV_ACTION_BOATE,   EV_ACTION_DJEEP,     EV_ACTION_FLAG,
  EV_ACTION_EXTRAIT,  EV_ACTION_FABJEEP, EV_ACTION_FABMINE,   EV_ACTION_FABDISC,
  EV_ACTION_REPEAT,   EV_ACTION_DARMURE, EV_ACTION_FABARMURE,
};

// Supprime tous les blupi.

void
CDecor::BlupiFlush ()
{
  Sint32 i;

  memset (m_blupi, 0, sizeof (Blupi) * MAXBLUPI);

  for (i = 0; i < MAXBLUPI; i++)
    m_blupi[i].bExist = false;
}

// Crée un nouveau blupi, et retourne son rang.

Sint32
CDecor::BlupiCreate (
  Point cel, Sint32 action, Sint32 direct, Sint32 perso, Sint32 energy)
{
  Sint32 rank;

  if (
    perso == 0 && action == ACTION_STOP && // blupi ?
    energy <= MAXENERGY / 4)
    action = ACTION_STOPTIRED;

  if (perso == 1 && action == ACTION_STOP) // araignée ?
    action = ACTION_S_STOP;

  if (perso == 2 && action == ACTION_STOP) // virus ?
    action = ACTION_V_STOP;

  if (perso == 3 && action == ACTION_STOP) // tracks ?
    action = ACTION_T_STOP;

  if (perso == 4 && action == ACTION_STOP) // robot ?
    action = ACTION_R_STOP;

  if (perso == 5 && action == ACTION_STOP) // bombe ?
    action = ACTION_B_STOP;

  if (perso == 7 && action == ACTION_STOP) // électro ?
    action = ACTION_E_STOP;

  if (perso == 8 && action == ACTION_STOP) // disciple ?
    action = ACTION_D_STOP;

  for (rank = 0; rank < MAXBLUPI; rank++)
  {
    if (
      m_blupi[rank].bExist && m_blupi[rank].cel.x == cel.x &&
      m_blupi[rank].cel.y == cel.y)
    {
      m_blupi[rank].aDirect = ((m_blupi[rank].aDirect / 16 + 1) % 8) * 16;
      m_blupi[rank].sDirect = m_blupi[rank].aDirect;
      m_blupi[rank].perso   = perso;
      m_blupi[rank].energy  = energy;
      m_blupi[rank].action  = action;
      BlupiActualise (rank);
      return rank;
    }
  }

  for (rank = 0; rank < MAXBLUPI; rank++)
  {
    if (!m_blupi[rank].bExist)
    {
      m_blupi[rank].bExist      = true;
      m_blupi[rank].bHili       = false;
      m_blupi[rank].perso       = perso;
      m_blupi[rank].energy      = energy;
      m_blupi[rank].goalAction  = 0;
      m_blupi[rank].goalCel.x   = -1;
      m_blupi[rank].goalCel.y   = -1;
      m_blupi[rank].cel         = cel;
      m_blupi[rank].fix         = cel;
      m_blupi[rank].action      = action;
      m_blupi[rank].interrupt   = 1;
      m_blupi[rank].aDirect     = direct;
      m_blupi[rank].sDirect     = direct;
      m_blupi[rank].phase       = 0;
      m_blupi[rank].step        = rank * 13; // un peu de hazard !
      m_blupi[rank].channel     = -1;
      m_blupi[rank].icon        = -1;
      m_blupi[rank].lastIcon    = -1;
      m_blupi[rank].pos.x       = 0;
      m_blupi[rank].pos.y       = 0;
      m_blupi[rank].posZ        = 0;
      m_blupi[rank].takeChannel = -1;
      m_blupi[rank].passCel.x   = -1;
      m_blupi[rank].jaugePhase  = -1;
      m_blupi[rank].jaugeMax    = -1;
      m_blupi[rank].stop        = 0;
      m_blupi[rank].bArrow      = false;
      m_blupi[rank].bRepeat     = false;
      m_blupi[rank].bMalade     = false;
      m_blupi[rank].bCache      = false;
      m_blupi[rank].vehicule    = 0;
      m_blupi[rank].busyCount   = 0;
      m_blupi[rank].busyDelay   = 0;
      m_blupi[rank].clicCount   = 0;
      m_blupi[rank].clicDelay   = 0;

      ListFlush (rank);
      FlushUsed (rank);
      BlupiDestCel (rank);
      BlupiPushFog (rank);
      BlupiActualise (rank);
      return rank;
    }
  }

  return -1;
}

// Supprime un blupi existant.
// Si perso == -1, supprime n'importe quel personnage ici.
// Si perso >= 0, supprime seulement ce personnage.

bool
CDecor::BlupiDelete (Point cel, Sint32 perso)
{
  Sint32 rank;

  for (rank = 0; rank < MAXBLUPI; rank++)
  {
    if (
      m_blupi[rank].bExist && m_blupi[rank].cel.x == cel.x &&
      m_blupi[rank].cel.y == cel.y &&
      (perso == -1 || m_blupi[rank].perso == perso))
    {
      m_blupi[rank].bExist = false;

      if (!m_bBuild) // phase de jeu ?
      {
        if (perso == 6) // détonnateur invisible ?
        {
          MoveFinish (rank); // stoppe décompte à rebourd
        }
        if (m_nbBlupiHili > 0 && m_rankBlupiHili == rank) // est-ce le blupi
                                                          // sélectionné ?
        {
          m_nbBlupiHili   = 0;
          m_rankBlupiHili = -1;
        }
      }
      return true;
    }
  }

  return false;
}

// Supprime un blupi existant.

void
CDecor::BlupiDelete (Sint32 rank)
{
  m_blupi[rank].bExist = false;

  if (
    !m_bBuild && // phase de jeu ?
    m_nbBlupiHili > 0 &&
    m_rankBlupiHili == rank) // est-ce le blupi sélectionné ?
  {
    m_nbBlupiHili   = 0;
    m_rankBlupiHili = -1;
  }
}

// Supprime tout dans un périmètre donné suite à une explosion.
//  type=0  ->  explosion
//  type=1  ->  électro

void
CDecor::BlupiKill (Sint32 exRank, Point cel, Sint32 type)
{
  Sint32 rank, action, x, y, icon;

  for (rank = 0; rank < MAXBLUPI; rank++)
  {
    // Supprime sans condition les blupi placés
    // dans la cellule où a lieu l'explosion.
    if (
      rank != exRank && m_blupi[rank].bExist &&
      m_blupi[rank].vehicule != 3 && // pas armure ?
      !m_bInvincible && m_blupi[rank].cel.x >= cel.x &&
      m_blupi[rank].cel.x <= cel.x + 1 && m_blupi[rank].cel.y >= cel.y &&
      m_blupi[rank].cel.y <= cel.y + 1)
    {
      GoalUnwork (rank);

      if (type == 0) // explosion ?
      {
        m_blupi[rank].bExist = false; // mort instantannée
      }

      if (type == 1) // électro ?
      {
        x    = m_blupi[rank].cel.x;
        y    = m_blupi[rank].cel.y;
        icon = m_decor[x / 2][y / 2].objectIcon;
        if (
          m_blupi[rank].perso == 0 && m_blupi[rank].vehicule == 0 && // à pied ?
          !m_bInvincible && icon != 113 &&                           // maison ?
          icon != 28 && icon != 29 &&   // laboratoire ?
          icon != 119 && icon != 120 && // usine ?
          icon != 121 && icon != 122)   // mine de fer ?
        {
          if (m_blupi[rank].bMalade)
            action = EV_ACTION_ELECTROm;
          else
            action = EV_ACTION_ELECTRO;
          GoalStart (rank, action, m_blupi[rank].cel);
          //?                 BlupiChangeAction(rank, ACTION_ELECTRO);
        }
      }
    }

    // Supprime les blupi placés une case autour de la
    // cellule où a lieu l'explosion, seulement s'ils
    // ne sont pas cachés (pas dans un batiment).
    if (
      type == 0 && rank != exRank && m_blupi[rank].bExist &&
      m_blupi[rank].vehicule != 3 &&             // pas armure ?
      !m_bInvincible && !m_blupi[rank].bCache && // pas dans un batiment ?
      m_blupi[rank].cel.x >= cel.x - 1 && m_blupi[rank].cel.x <= cel.x + 2 &&
      m_blupi[rank].cel.y >= cel.y - 1 && m_blupi[rank].cel.y <= cel.y + 2)
    {
      GoalUnwork (rank);
      m_blupi[rank].bExist = false;
    }
  }
}

// Test si un blupi existe.

bool
CDecor::BlupiIfExist (Sint32 rank)
{
  return !!m_blupi[rank].bExist;
}

// Triche pour tous les blupi.
//  #1  ->  (POWER)    redonne l'énergie maximale
//  #2  ->  (LONESOME) tue toutes les araignées/virus/etc.

void
CDecor::BlupiCheat (Sint32 cheat)
{
  Sint32 rank;

  for (rank = 0; rank < MAXBLUPI; rank++)
  {
    if (cheat == 1) // power ?
    {
      if (m_blupi[rank].bExist && m_blupi[rank].perso == 0)
      {
        m_blupi[rank].energy  = MAXENERGY;
        m_blupi[rank].bMalade = false;
      }
    }

    if (cheat == 2) // lonesome ?
    {
      if (
        m_blupi[rank].bExist && m_blupi[rank].perso != 0 &&
        m_blupi[rank].perso != 8) // araignée/virus/etc. ?
        m_blupi[rank].bExist = false;
    }
  }
}

// Actualise un blupi pour pouvoir le dessiner dans son état.

void
CDecor::BlupiActualise (Sint32 rank)
{
  Sounds sound;

  Action (
    m_blupi[rank].action, m_blupi[rank].aDirect, m_blupi[rank].phase,
    m_blupi[rank].step, m_blupi[rank].channel, m_blupi[rank].icon,
    m_blupi[rank].pos, m_blupi[rank].posZ, sound);
  BlupiAdaptIcon (rank);

  m_blupi[rank].lastIcon = -1;
  m_blupi[rank].phase    = 0;
  m_blupi[rank].pos.x    = 0;
  m_blupi[rank].pos.y    = 0;
  m_blupi[rank].posZ     = 0;
}

// Adapte une icône.

void
CDecor::BlupiAdaptIcon (Sint32 rank)
{
  Sint32 direct;

  if (m_blupi[rank].icon == -1)
    return;

  if (m_blupi[rank].bMalade) // malade ?
  {
    if (m_blupi[rank].icon >= 69 && m_blupi[rank].icon <= 92)
    {
      m_blupi[rank].icon += 100; // 169..192 (tout vert)
    }
    else if (m_blupi[rank].icon == 270) // écrasé ?
      m_blupi[rank].icon = 271;
    else if (
      m_blupi[rank].icon < 318 || // pas télétransporté ?
      m_blupi[rank].icon > 321)
    {
      direct = GetIconDirect (m_blupi[rank].icon);
      if (direct < 0)
      {
        m_blupi[rank].icon = 169; // debout direction est !
      }
      else
        m_blupi[rank].icon = 169 + 3 * (direct / 16);
    }
  }

  if (m_blupi[rank].perso == 0 && m_blupi[rank].vehicule == 1) // en bateau ?
  {
    direct = GetIconDirect (m_blupi[rank].icon);
    if (direct < 0)
    {
      m_blupi[rank].icon = 234; // bateau direction est !
    }
    else
      m_blupi[rank].icon = 234 + (direct / (16 / 2));
  }

  if (m_blupi[rank].perso == 0 && m_blupi[rank].vehicule == 2) // en jeep ?
  {
    direct = GetIconDirect (m_blupi[rank].icon);
    if (direct < 0)
    {
      m_blupi[rank].icon = 250; // bateau direction est !
    }
    else
      m_blupi[rank].icon = 250 + (direct / (16 / 2));
  }

  if (m_blupi[rank].perso == 0 && m_blupi[rank].vehicule == 3) // armure ?
  {
    if (m_blupi[rank].icon < 322 || m_blupi[rank].icon > 347)
    {
      if (
        m_blupi[rank].icon == 106 || // élan pour saut ?
        m_blupi[rank].icon == 194)   // mèche ?
        m_blupi[rank].icon = 347;
      else
      {
        direct = GetIconDirect (m_blupi[rank].icon);
        if (direct < 0)
        {
          m_blupi[rank].icon = 322; // armure direction est !
        }
        else
          m_blupi[rank].icon = 322 + 3 * (direct / 16);
      }
    }
  }

  if (m_blupi[rank].perso == 8) // disciple ?
  {
    direct = GetIconDirect (m_blupi[rank].icon);
    if (direct < 0)
    {
      m_blupi[rank].icon = 290; // disciple direction est !
    }
    else
      m_blupi[rank].icon = 290 + (direct / (16 / 2));
  }
}

// Fait entendre un son pour un blupi.
// Si bStop=true, on stoppe le son précédent associé
// à ce blupi (rank), si nécessaire.

void
CDecor::BlupiSound (Sint32 rank, Sounds sound, Point pos, bool bStop)
{
  Sounds newSound;

  if (rank == -1)
    rank = m_rankBlupiHili;

  if (rank != -1 && m_blupi[rank].perso == 8) // disciple ?
  {
    if (
      sound == SOUND_HOP || sound == SOUND_BURN || sound == SOUND_TCHAO ||
      sound == SOUND_FLOWER || sound == SOUND_ARROSE)
      newSound = SOUND_NONE;
    else
      newSound = sound;

    if (
      sound == SOUND_BOING || sound == SOUND_BOING1 || sound == SOUND_BOING2 ||
      sound == SOUND_BOING3)
      newSound = SOUND_D_BOING;
    if (
      sound == SOUND_GO1 || sound == SOUND_GO2 || sound == SOUND_GO3 ||
      sound == SOUND_GO4 || sound == SOUND_GO5 || sound == SOUND_GO6)
      newSound = SOUND_D_GO;
    if (
      sound == SOUND_OK1 || sound == SOUND_OK2 || sound == SOUND_OK3 ||
      sound == SOUND_OK4 || sound == SOUND_OK5 || sound == SOUND_OK6 ||
      sound == SOUND_OK1f || sound == SOUND_OK2f || sound == SOUND_OK3f ||
      sound == SOUND_OK1e || sound == SOUND_OK2e || sound == SOUND_OK3e)
      newSound = SOUND_D_OK;
    if (
      sound == SOUND_TERM1 || sound == SOUND_TERM2 || sound == SOUND_TERM3 ||
      sound == SOUND_TERM4 || sound == SOUND_TERM5 || sound == SOUND_TERM6)
      newSound = SOUND_D_TERM;

    if (newSound == SOUND_NONE)
      return;
    sound = newSound;
  }

  if (bStop)
    m_pSound->PlayImage (sound, pos, rank);
  else
    m_pSound->PlayImage (sound, pos);
}

// Sons associés à des actions.
static const struct {
  Sint16 action;
  Sounds sound;
} tableSound[] = {
  {ACTION_BURN, SOUND_BURN},   {ACTION_TCHAO, SOUND_TCHAO},
  {ACTION_EAT, SOUND_EAT},     {ACTION_DRINK, SOUND_DRINK},
  {ACTION_SLIDE, SOUND_SLIDE}, {ACTION_R_LOAD, SOUND_R_LOAD},
};

// Effectue quelques initialisations pour une nouvelle action.

void
CDecor::BlupiInitAction (Sint32 rank, Sint32 action, Sint32 direct)
{
  Point  pos;
  Sint32 rand;

  for (size_t i = 0; i < countof (tableSound); ++i)
  {
    if (tableSound[i].action == action)
    {
      pos = ConvCelToPos (m_blupi[rank].cel);
      BlupiSound (rank, tableSound[i].sound, pos);
      break;
    }
  }

  m_blupi[rank].action = action;

  if (m_blupi[rank].perso == 0) // blupi ?
  {
    if (m_blupi[rank].vehicule == 1) // en bateau ?
    {
      if (m_blupi[rank].action == ACTION_STOP)
        m_blupi[rank].action = ACTION_STOPb;

      if (m_blupi[rank].action == ACTION_WALK)
        m_blupi[rank].action = ACTION_MARCHEb;
    }

    if (m_blupi[rank].vehicule == 2) // en jeep ?
    {
      if (m_blupi[rank].action == ACTION_STOP)
        m_blupi[rank].action = ACTION_STOPJEEP;

      if (m_blupi[rank].action == ACTION_WALK)
        m_blupi[rank].action = ACTION_WALKJEEP;
    }

    if (m_blupi[rank].vehicule == 3) // armure ?
    {
      if (m_blupi[rank].action == ACTION_STOP)
        m_blupi[rank].action = ACTION_STOPARMOR;

      if (m_blupi[rank].action == ACTION_WALK)
      {
        m_blupi[rank].action = ACTION_WALKARMOR;
        m_blupi[rank].step   = (m_blupi[rank].step / 2) * 2;
      }
    }

    if (m_blupi[rank].energy <= MAXENERGY / 4) // peu de forces ?
    {
      if (m_blupi[rank].action == ACTION_STOP)
        m_blupi[rank].action = ACTION_STOPTIRED;

      if (m_blupi[rank].action == ACTION_WALK)
      {
        m_blupi[rank].action = ACTION_WALKTIRED;
        m_blupi[rank].step   = (m_blupi[rank].step / 2) * 2;
      }
    }

    if (
      m_blupi[rank].action == ACTION_STOP &&
      m_blupi[rank].goalAction == 0) // à pied ?
    {
      rand = Random (0, 400);
      if (rand >= 10 && rand <= 15 && m_blupi[rank].takeChannel == -1)
      {
        m_blupi[rank].action = ACTION_MISC1; // épaules
        m_blupi[rank].step   = 0;
      }
      if (rand >= 20 && rand <= 23)
      {
        m_blupi[rank].action = ACTION_MISC2; // grat-grat
        m_blupi[rank].step   = 0;
      }
      if (rand == 30 && m_blupi[rank].takeChannel == -1)
      {
        m_blupi[rank].action = ACTION_MISC3; // yoyo
        m_blupi[rank].step   = 0;
      }
      if (rand >= 40 && rand <= 45)
      {
        m_blupi[rank].action = ACTION_MISC4; // ferme les yeux
        m_blupi[rank].step   = 0;
      }
      if (rand == 50 && m_blupi[rank].takeChannel == -1)
      {
        m_blupi[rank].action = ACTION_MISC5; // ohé
        m_blupi[rank].step   = 0;
      }
      if (rand == 60)
      {
        m_blupi[rank].action = ACTION_MISC6; // diabolo
        m_blupi[rank].step   = 0;
      }
    }
    if (
      m_blupi[rank].action == ACTION_STOPTIRED && m_blupi[rank].goalAction == 0)
    {
      rand = Random (0, 100);
      if (
        rand == 10 && // propabilité 1/100
        m_blupi[rank].takeChannel == -1)
      {
        m_blupi[rank].action = ACTION_MISC1f;
        m_blupi[rank].step   = 0;
      }
    }

    if (direct != -1)
      m_blupi[rank].sDirect = direct;

    if (
      m_blupi[rank].action == ACTION_BUILD ||
      m_blupi[rank].action == ACTION_BUILDBREF ||
      m_blupi[rank].action == ACTION_BUILDSEC ||
      m_blupi[rank].action == ACTION_BUILDSOURD ||
      m_blupi[rank].action == ACTION_BUILDPIERRE ||
      m_blupi[rank].action == ACTION_PICKAXE ||
      m_blupi[rank].action == ACTION_PIOCHEPIERRE ||
      m_blupi[rank].action == ACTION_PIOCHESOURD ||
      m_blupi[rank].action == ACTION_ARROSE ||
      m_blupi[rank].action == ACTION_BECHE ||
      m_blupi[rank].action == ACTION_SAW ||
      m_blupi[rank].action == ACTION_CARRY ||
      m_blupi[rank].action == ACTION_DROP ||
      m_blupi[rank].action == ACTION_BURN ||
      m_blupi[rank].action == ACTION_TCHAO ||
      m_blupi[rank].action == ACTION_GRILL1 ||
      m_blupi[rank].action == ACTION_GRILL2 ||
      m_blupi[rank].action == ACTION_GRILL3 ||
      m_blupi[rank].action == ACTION_ELECTRO ||
      m_blupi[rank].action == ACTION_EAT ||
      m_blupi[rank].action == ACTION_DRINK ||
      m_blupi[rank].action == ACTION_BORN ||
      m_blupi[rank].action == ACTION_JUMPJEEP ||
      m_blupi[rank].action == ACTION_JUMP2 ||
      m_blupi[rank].action == ACTION_JUMP3 ||
      m_blupi[rank].action == ACTION_JUMP4 ||
      m_blupi[rank].action == ACTION_JUMP5 ||
      //?          m_blupi[rank].action == ACTION_GLISSE       ||
      m_blupi[rank].action == ACTION_BRIDGE ||
      m_blupi[rank].action == ACTION_MECHE ||
      m_blupi[rank].action == ACTION_S_GRILL ||
      m_blupi[rank].action == ACTION_V_GRILL ||
      m_blupi[rank].action == ACTION_T_CRUSHED ||
      m_blupi[rank].action == ACTION_R_WALK ||
      m_blupi[rank].action == ACTION_R_APLAT ||
      m_blupi[rank].action == ACTION_R_BUILD ||
      m_blupi[rank].action == ACTION_R_LOAD ||
      m_blupi[rank].action == ACTION_B_WALK ||
      m_blupi[rank].action == ACTION_E_WALK ||
      m_blupi[rank].action == ACTION_MARCHEb ||
      m_blupi[rank].action == ACTION_WALKJEEP ||
      m_blupi[rank].action == ACTION_TELEPORTE1 ||
      m_blupi[rank].action == ACTION_TELEPORTE2 ||
      m_blupi[rank].action == ACTION_TELEPORTE3 ||
      m_blupi[rank].action == ACTION_ARMOROPEN ||
      m_blupi[rank].action == ACTION_ARMORCLOSE)
      m_blupi[rank].step = 0;
  }

  if (m_blupi[rank].perso == 1) // araignée ?
  {
    if (m_blupi[rank].action == ACTION_WALK)
      m_blupi[rank].action = ACTION_S_WALK;

    if (m_blupi[rank].action == ACTION_STOP)
    {
      m_blupi[rank].action  = ACTION_S_STOP;
      m_blupi[rank].sDirect = Random (0, 7) * 16;
    }

    m_blupi[rank].step = 0;
  }

  if (m_blupi[rank].perso == 2) // virus ?
  {
    if (m_blupi[rank].action == ACTION_WALK)
      m_blupi[rank].action = ACTION_V_WALK;

    if (m_blupi[rank].action == ACTION_STOP)
      m_blupi[rank].action = ACTION_V_STOP;

    m_blupi[rank].step = 0;
  }

  if (m_blupi[rank].perso == 3) // tracks ?
  {
    if (m_blupi[rank].action == ACTION_WALK)
      m_blupi[rank].action = ACTION_T_WALK;

    if (m_blupi[rank].action == ACTION_STOP)
      m_blupi[rank].action = ACTION_T_STOP;

    m_blupi[rank].step = 0;
  }

  if (m_blupi[rank].perso == 4) // robot ?
  {
    if (m_blupi[rank].action == ACTION_WALK)
      m_blupi[rank].action = ACTION_R_WALK;

    if (m_blupi[rank].action == ACTION_STOP)
      m_blupi[rank].action = ACTION_R_STOP;

    m_blupi[rank].step = 0;
  }

  if (m_blupi[rank].perso == 5) // bombe ?
  {
    if (m_blupi[rank].action == ACTION_WALK)
      m_blupi[rank].action = ACTION_B_WALK;

    if (m_blupi[rank].action == ACTION_STOP)
      m_blupi[rank].action = ACTION_B_STOP;

    m_blupi[rank].step = 0;
  }

  if (m_blupi[rank].perso == 7) // électro ?
  {
    if (m_blupi[rank].action == ACTION_WALK)
      m_blupi[rank].action = ACTION_E_WALK;

    if (m_blupi[rank].action == ACTION_STOP)
      m_blupi[rank].action = ACTION_E_STOP;

    m_blupi[rank].step = 0;
  }

  if (m_blupi[rank].perso == 8) // disciple ?
  {
    if (direct != -1)
      m_blupi[rank].sDirect = direct;

    if (m_blupi[rank].action == ACTION_WALK)
      m_blupi[rank].action = ACTION_D_WALK;

    if (m_blupi[rank].action == ACTION_STOP)
      m_blupi[rank].action = ACTION_D_STOP;

    if (
      m_blupi[rank].action == ACTION_PICKAXE ||
      m_blupi[rank].action == ACTION_BUILDSEC ||
      m_blupi[rank].action == ACTION_BUILDBREF)
      m_blupi[rank].action = ACTION_D_PICKAXE;

    if (m_blupi[rank].action == ACTION_BUILD)
      m_blupi[rank].action = ACTION_D_BUILD;

    if (
      m_blupi[rank].action == ACTION_SAW ||
      m_blupi[rank].action == ACTION_BUILDSOURD ||
      m_blupi[rank].action == ACTION_PIOCHESOURD)
      m_blupi[rank].action = ACTION_D_SAW;

    if (m_blupi[rank].action == ACTION_TCHAO)
      m_blupi[rank].action = ACTION_D_TCHAO;

    if (m_blupi[rank].action == ACTION_CUEILLE1)
      m_blupi[rank].action = ACTION_D_CUEILLE1;

    if (m_blupi[rank].action == ACTION_CUEILLE2)
      m_blupi[rank].action = ACTION_D_CUEILLE2;

    if (m_blupi[rank].action == ACTION_MECHE)
      m_blupi[rank].action = ACTION_D_MECHE;

    if (m_blupi[rank].action == ACTION_ARROSE)
      m_blupi[rank].action = ACTION_D_ARROSE;

    if (m_blupi[rank].action == ACTION_BECHE)
      m_blupi[rank].action = ACTION_D_BECHE;

    m_blupi[rank].step = 0;
  }
}

// Change l'action de blupi.

void
CDecor::BlupiChangeAction (Sint32 rank, Sint32 action, Sint32 direct)
{
  if (rank < 0)
    return;

  BlupiInitAction (rank, action, direct);
  BlupiDestCel (rank);
  m_blupi[rank].phase = 0;
  m_blupi[rank].pos.x = 0;
  m_blupi[rank].pos.y = 0;
  BlupiNextAction (rank);
}

// Vide la liste des actions.

void
CDecor::ListFlush (Sint32 rank)
{
  Sint32 i;

  for (i = 0; i < MAXLIST; i++)
    m_blupi[rank].listButton[i] = BUTTON_NONE;
  m_blupi[rank].repeatLevelHope = -1;
  m_blupi[rank].repeatLevel     = -1;
}

// Retourne le paramètre associé à une action.

Sint32
CDecor::ListGetParam (Sint32 rank, Buttons button, Point cel)
{
  Sint32 icon;

  if (button == BUTTON_CARRY || button == BUTTON_FLOWER)
    return m_decor[cel.x / 2][cel.y / 2].objectIcon;

  if (button == BUTTON_DEPOSE)
    return m_blupi[rank].takeIcon;

  if (button == BUTTON_GO && cel.x % 2 == 1 && cel.y % 2 == 1)
  {
    icon = m_decor[cel.x / 2][cel.y / 2].objectIcon;
    if (
      icon == 117 || // bateau ?
      icon == 118)   // voiture ?
      return icon;
  }

  return -1;
}

// Ajoute une action dans la liste.

bool
CDecor::ListPut (Sint32 rank, Buttons button, Point cel, Point cMem)
{
  Sint32 i, last;

  if (button == BUTTON_REPEAT || button == BUTTON_GO)
    return true;

  // Mémorise "mange" seulement après un "cultive".
  if (button == BUTTON_EAT && m_blupi[rank].listButton[0] != BUTTON_CULTIVE)
    return true;

  // Si prend/dépose à la suite au même endroit,
  // il est inutile de mémoriser !
  last = m_blupi[rank].listButton[0];
  if (
    (button == BUTTON_CARRY && last == BUTTON_DEPOSE) ||
    (button == BUTTON_DEPOSE && last == BUTTON_CARRY))
  {
    if (
      cel.x / 2 == m_blupi[rank].listCel[0].x / 2 &&
      cel.y / 2 == m_blupi[rank].listCel[0].y / 2)
    {
      ListRemove (rank);
      return true;
    }
  }

  for (i = MAXLIST - 1; i > 0; i--)
  {
    m_blupi[rank].listButton[i] = m_blupi[rank].listButton[i - 1];
    m_blupi[rank].listCel[i]    = m_blupi[rank].listCel[i - 1];
    m_blupi[rank].listParam[i]  = m_blupi[rank].listParam[i - 1];
  }

  m_blupi[rank].listButton[0] = button;
  m_blupi[rank].listCel[0]    = cMem;
  m_blupi[rank].listParam[0]  = ListGetParam (rank, button, cel);

  return true;
}

// Enlève la dernière action ajoutée dans la liste.

void
CDecor::ListRemove (Sint32 rank)
{
  Sint32 i;

  if (m_blupi[rank].listButton[0] == BUTTON_CULTIVE)
    return;

  for (i = 0; i < MAXLIST - 1; i++)
  {
    m_blupi[rank].listButton[i] = m_blupi[rank].listButton[i + 1];
    m_blupi[rank].listCel[i]    = m_blupi[rank].listCel[i + 1];
    m_blupi[rank].listParam[i]  = m_blupi[rank].listParam[i + 1];
  }

  m_blupi[rank].listButton[MAXLIST - 1] = BUTTON_NONE;
}

// Cherche une action à répéter dans la liste.
// Retourne la profondeur de la répétition.
// Retourne -1 si aucune répétiton n'est possible.

Sint32
CDecor::ListSearch (
  Sint32 rank, Buttons button, Point cel, const char *& textForButton)
{
  Sint32 i, j, param, nb;

  static const char * errors[] = {
    /*  0 */ translate ("1: Grow tomatoes\n2: Eat"),
    /*  1 */ translate ("1: Make a bunch\n2: Transform"),
    /*  2 */ translate ("1: Take\n2: Transform"),
    /*  3 */ translate ("1: Extract iron\n2: Make a bomb"),
    /*  4 */ translate ("1: Extract iron\n2: Make a Jeep"),
    /*  5 */ translate ("1: Extract iron\n2: Make an armour"),
    /*  6 */ translate ("1: Cut down a tree \n2: Make a palisade"),
    /*  7 */ translate ("1: Take\n2: Build palisade"),
    /*  8 */ translate ("1: Cut down a tree \n2: Build a bridge"),
    /*  9 */ translate ("1: Take\n2: Build a bridge"),
    /* 10 */ translate ("1: Cut down a tree \n2: Make a boat"),
    /* 11 */ translate ("1: Take\n2: Make a boat"),
  };

  static Sint32 table_series[] = {
    0, // errors
    2,  BUTTON_CULTIVE, BUTTON_EAT,

    1, // errors
    4,  BUTTON_FLOWER,  BUTTON_CARRY,     BUTTON_LABO,   BUTTON_DEPOSE,

    2, // errors
    3,  BUTTON_CARRY,   BUTTON_LABO,      BUTTON_DEPOSE,

    3, // errors
    3,  BUTTON_EXTRAIT, BUTTON_FABMINE,   BUTTON_DEPOSE,

    4, // errors
    3,  BUTTON_EXTRAIT, BUTTON_FABJEEP,   BUTTON_DJEEP,

    5, // errors
    3,  BUTTON_EXTRAIT, BUTTON_MAKEARMOR, BUTTON_DARMOR,

    6, // errors
    4,  BUTTON_ABAT,    BUTTON_CARRY,     BUTTON_DEPOSE, BUTTON_PALIS,

    7, // errors
    3,  BUTTON_CARRY,   BUTTON_DEPOSE,    BUTTON_PALIS,

    8, // errors
    4,  BUTTON_ABAT,    BUTTON_CARRY,     BUTTON_DEPOSE, BUTTON_BRIDGE,

    9, // errors
    3,  BUTTON_CARRY,   BUTTON_DEPOSE,    BUTTON_BRIDGE,

    10, // errors
    4,  BUTTON_ABAT,    BUTTON_CARRY,     BUTTON_DEPOSE, BUTTON_BOAT,

    11, // errors
    3,  BUTTON_CARRY,   BUTTON_DEPOSE,    BUTTON_BOAT,

    -1,
  };

  param = ListGetParam (rank, button, cel);

  i = 0;
  while (table_series[i] != -1)
  {
    nb = table_series[i + 1];
    if (
      button == m_blupi[rank].listButton[nb - 1] &&
      param == m_blupi[rank].listParam[nb - 1] &&
      cel.x >= m_blupi[rank].listCel[nb - 1].x - 50 &&
      cel.x <= m_blupi[rank].listCel[nb - 1].x + 50 &&
      cel.y >= m_blupi[rank].listCel[nb - 1].y - 50 &&
      cel.y <= m_blupi[rank].listCel[nb - 1].y + 50)
    {
      for (j = 0; j < nb; j++)
      {
        if (table_series[i + 2 + j] != m_blupi[rank].listButton[nb - 1 - j])
          goto next;
      }
      textForButton = gettext (errors[table_series[i]]);
      return nb - 1;
    }

  next:
    i += nb + 2;
  }

#if 0
    for (i = 0 ; i < MAXLIST ; i++)
    {
        if (button == m_blupi[rank].listButton[i]   &&
            param  == m_blupi[rank].listParam[i]    &&
            cel.x  >= m_blupi[rank].listCel[i].x - 50 &&
            cel.x  <= m_blupi[rank].listCel[i].x + 50 &&
            cel.y  >= m_blupi[rank].listCel[i].y - 50 &&
            cel.y  <= m_blupi[rank].listCel[i].y + 50)
            return i;
    }
#endif

  return -1;
}

// Ajuste une action à répéter.

bool
CDecor::RepeatAdjust (
  Sint32 rank, Sint32 button, Point & cel, Point & cMem, Sint32 param,
  Sint32 list)
{
  Sint32 i, channel, icon, icon1, icon2, flags;
  Point  test;

  static Sint32 table_object[] = {
    BUTTON_ABAT,
    CHOBJECT,
    6,
    11,
    BUTTON_ROC,
    CHOBJECT,
    37,
    43,
    BUTTON_EAT,
    CHOBJECT,
    60,
    60,
    BUTTON_PALIS,
    CHOBJECT,
    36,
    36,
    BUTTON_BOAT,
    CHOBJECT,
    36,
    36,
    BUTTON_DEPOSE,
    -1,
    -1,
    -1,
    BUTTON_DJEEP,
    -1,
    -1,
    -1,
    BUTTON_DARMOR,
    -1,
    -1,
    -1,
    0,
  };

  static Sint32 table_mur[] = {
    +2, 0,  // 1<<0
    0,  +2, // 1<<1
    -2, 0,  // 1<<2
    0,  -2, // 1<<3
  };

  if (
    button == BUTTON_DEPOSE && // dépose pour une palissade ?
    list > 0 && m_blupi[rank].listButton[list - 1] == BUTTON_PALIS)
  {
    icon = m_decor[cel.x / 2][cel.y / 2].objectIcon;

    flags = 0;
    if (icon == 65)
      flags = (1 << 0) | (1 << 2);
    if (icon == 66)
      flags = (1 << 1) | (1 << 3);
    if (icon == 67)
      flags = (1 << 0) | (1 << 1);
    if (icon == 68)
      flags = (1 << 1) | (1 << 2);
    if (icon == 69)
      flags = (1 << 2) | (1 << 3);
    if (icon == 70)
      flags = (1 << 0) | (1 << 3);
    if (icon == 71)
    {
      for (i = 0; i < 4; i++)
      {
        test.x = cel.x + table_mur[i * 2 + 0];
        test.y = cel.y + table_mur[i * 2 + 1];
        if (
          IsValid (test) &&
          m_decor[test.x / 2][test.y / 2].floorIcon == 15 && // dalle grise ?
          CelOkForAction (test, EV_ACTION_DROP, rank) == 0)
        {
          cel  = test;
          cMem = test;
          goto ok;
        }
      }
      flags = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3);
    }

    if (flags == 0)
    {
      if (CelOkForAction (cel, EV_ACTION_DROP, rank) == 0)
        goto ok;
    }

    for (i = 0; i < 4; i++)
    {
      if (flags & (1 << i))
      {
        test.x = cel.x + table_mur[i * 2 + 0];
        test.y = cel.y + table_mur[i * 2 + 1];
        if (CelOkForAction (test, EV_ACTION_DROP, rank) == 0)
        {
          cel  = test;
          cMem = test;
          goto ok;
        }
      }
    }
    return false;
  }

  if (
    button == BUTTON_DEPOSE && // dépose pour un bateau ?
    list > 0 && m_blupi[rank].listButton[list - 1] == BUTTON_BOAT)
  {
    if (SearchOtherBateau (rank, cel, 100, test, icon))
    {
      cel  = test;
      cMem = test;
      goto ok;
    }
    return false;
  }

  //? if ( button == BUTTON_MANGE )
  //? {
  //?     cel = m_blupi[rank].cel;  // cherche là où est blupi !
  //? }

  i       = 0;
  channel = -2;
  icon1   = -1;
  icon2   = -1;
  while (table_object[i] != 0)
  {
    if (button == table_object[i])
    {
      channel = table_object[i + 1];
      icon1   = table_object[i + 2];
      icon2   = table_object[i + 3];
      break;
    }
    i += 4;
  }

  if (button == BUTTON_CARRY || button == BUTTON_FLOWER)
  {
    channel = CHOBJECT;
    icon1   = param;
    icon2   = param;
  }
  if (
    button == BUTTON_GO && // va en bateau/jeep ?
    param != -1)
  {
    channel = CHOBJECT;
    icon1   = param;
    icon2   = param;
  }

  if (channel != -2)
  {
    if (!SearchOtherObject (
          rank, cel, table_actions[button], 50 * 2, channel, icon1, icon2, -1,
          -1, cel, icon1))
      return false;
  }

  if (button == BUTTON_PALIS)
    cMem = cel;

ok:
  if (cel.x % 2 == 0)
    cel.x++;
  if (cel.y % 2 == 0)
    cel.y++; // sur l'objet

  m_blupi[rank].interrupt = 1;
  return true;
}

// Démarre une action.

void
CDecor::GoalStart (Sint32 rank, Sint32 action, Point cel)
{
  m_blupi[rank].goalHili   = cel;
  m_blupi[rank].goalAction = action;
  m_blupi[rank].goalPhase  = 0;
  m_blupi[rank].goalCel.x  = -1;
  m_blupi[rank].bRepeat    = false;

  GoalInitJauge (rank);
  FlushUsed (rank);
}

// Effectue la méta opération suivante.
// Retourne false lorsque c'est fini !

bool
CDecor::GoalNextPhase (Sint32 rank)
{
  Sint16 * pTable;
  Sint32   i, nb;

  if (m_blupi[rank].goalAction == 0)
    return false;

  pTable = GetTableGoal (m_blupi[rank].goalAction);
  if (pTable == nullptr)
  {
    GoalStop (rank, true);
    return false;
  }

  for (i = 0; i < m_blupi[rank].goalPhase; i++)
  {
    if (*pTable == 0)
      return false;
    pTable += 1 + table_goal_nbop[*pTable];
  }

  if (*pTable == GOAL_GROUP)
  {
    m_blupi[rank].goalPhase++;
    nb = pTable[1];
    pTable += 2;

    for (i = 0; i < nb; i++)
    {
      m_blupi[rank].goalPhase++;
      if (!GoalNextOp (rank, pTable))
        return false;
      pTable += 1 + table_goal_nbop[*pTable];
    }
  }

  m_blupi[rank].goalPhase++;
  return GoalNextOp (rank, pTable);
}

// Initialise la jauge pour une méta opération.

void
CDecor::GoalInitJauge (Sint32 rank)
{
  Sint16 * pTable;
  Sint32   max = 0, op;

  m_blupi[rank].jaugePhase = -1;
  m_blupi[rank].jaugeMax   = -1;

  if (m_blupi[rank].perso != 0 && m_blupi[rank].perso != 8)
    return; // araignée/virus/etc. ?
  if (m_blupi[rank].goalAction == 0)
    return;

  pTable = GetTableGoal (m_blupi[rank].goalAction);
  if (pTable == nullptr)
    goto term;

  while (true)
  {
    op = *pTable;
    if (op == 0 || op == GOAL_TERM)
      goto term;

    if (op == GOAL_ACTION && pTable[1] != ACTION_STOP)
      max++;

    pTable += 1 + table_goal_nbop[*pTable];
  }

term:
  if (max > 0)
  {
    m_blupi[rank].jaugePhase = 0;
    m_blupi[rank].jaugeMax   = max;
  }
}

// Permet de passer à travers certains arbres.

void
CDecor::GoalInitPassCel (Sint32 rank)
{
  Point  cel;
  Sint32 channel, icon;

  cel.x = (m_blupi[rank].goalCel.x / 2) * 2;
  cel.y = (m_blupi[rank].goalCel.y / 2) * 2;

  GetObject (cel, channel, icon);

  if (
    channel == CHOBJECT &&
    ((icon >= 8 && icon <= 11) ||  // arbres touffus ?
     (icon >= 30 && icon <= 35) || // arbres touffus sans feuilles ?
     (icon >= 37 && icon <= 43) || // rochers ?
     (icon == 81 || icon == 83 || icon == 94) || // fleurs ?
     (icon >= 100 && icon <= 105) ||             // usine ?
     (icon == 115 || icon == 116) ||             // usine ?
     (icon == 17 || icon == 18) ||               // usine ?
     (icon == 117) ||                            // bateau ?
     (icon == 118) ||                            // jeep ?
     (icon == 16)))                              // armure ?
    m_blupi[rank].passCel = m_blupi[rank].goalCel;
  else
    m_blupi[rank].passCel.x = -1;
}

// Ajuste une coordonnée de cellule.

void
CDecor::GoalAdjustCel (Sint32 rank, Sint32 & x, Sint32 & y)
{
  if (x == -10 && y == -10)
  {
    if (m_blupi[rank].goalAction == EV_ACTION_BRIDGEEL)
    {
      x = m_blupi[rank].fix.x + m_blupi[rank].cLoop * 2;
      y = m_blupi[rank].fix.y;
      return;
    }
    if (m_blupi[rank].goalAction == EV_ACTION_BRIDGEOL)
    {
      x = m_blupi[rank].fix.x - m_blupi[rank].cLoop * 2;
      y = m_blupi[rank].fix.y;
      return;
    }
    if (m_blupi[rank].goalAction == EV_ACTION_BRIDGESL)
    {
      x = m_blupi[rank].fix.x;
      y = m_blupi[rank].fix.y + m_blupi[rank].cLoop * 2;
      return;
    }
    if (m_blupi[rank].goalAction == EV_ACTION_BRIDGENL)
    {
      x = m_blupi[rank].fix.x;
      y = m_blupi[rank].fix.y - m_blupi[rank].cLoop * 2;
      return;
    }
  }

  x += m_blupi[rank].cel.x;
  y += m_blupi[rank].cel.y;
}

// Liste des buts multiples.

Sint32 table_multi_goal[16 * 2] = {
  0,  0,  +1, 0, 0,  +1, +1, +1, 0,  -1, +1, -1, 0,  +2, +1, +2,
  -1, -1, -1, 0, -1, +1, -1, +2, +2, -1, +2, 0,  +2, +1, +2, +2,
};

// Effectue une méta opération.

bool
CDecor::GoalNextOp (Sint32 rank, Sint16 * pTable)
{
  Sint32  op, x, y;
  Sint32  action, direct, channel, icon, mchannel, micon;
  Sint32  total, step, delai, first, last, first2, last2, flag, i;
  Sint32  param;
  Buttons button;
  Point   pos, cel, cMem, destCel;
  bool    bOK, bError = true;

  pos = ConvCelToPos (m_blupi[rank].cel);

  op = *pTable++;

  if (op == GOAL_ACTION && *pTable != ACTION_STOP)
    m_blupi[rank].jaugePhase++;

  if (op == GOAL_GOHILI)
  {
    m_blupi[rank].goalCel.x = m_blupi[rank].goalHili.x + (*pTable++);
    m_blupi[rank].goalCel.y = m_blupi[rank].goalHili.y + (*pTable++);
    flag                    = *pTable++;
    //?     m_blupi[rank].passCel.x = -1;
    FlushUsed (rank);
    return true;
  }

  if (op == GOAL_GOHILI2)
  {
    cel.x = (m_blupi[rank].goalHili.x / 2) * 2 + (*pTable++);
    cel.y = (m_blupi[rank].goalHili.y / 2) * 2 + (*pTable++);
    flag  = *pTable++;
    if (!!flag)
    {
      m_blupi[rank].goalCel = cel;
      GoalInitPassCel (rank);
    }
    m_blupi[rank].goalCel = cel;
    FlushUsed (rank);
    return true;
  }

  if (op == GOAL_GOBLUPI)
  {
    cel.x = m_blupi[rank].cel.x + (*pTable++);
    cel.y = m_blupi[rank].cel.y + (*pTable++);
    flag  = *pTable++;
    if (!!flag)
    {
      if (IsBlupiHereEx (cel, rank, false)) // destination occupée ?
      {
        m_blupi[rank].goalPhase--; // on attend ...
        return true;
      }
      m_blupi[rank].goalCel = cel;
      GoalInitPassCel (rank);
    }
    m_blupi[rank].goalCel = cel;
    FlushUsed (rank);
    return true;
  }

  if (op == GOAL_TESTOBJECT)
  {
    cel.x   = m_blupi[rank].cel.x + (*pTable++);
    cel.y   = m_blupi[rank].cel.y + (*pTable++);
    channel = *pTable++;
    icon    = *pTable++;
    GetObject (cel, mchannel, micon);
    if (channel != mchannel || icon != micon)
      goto error;
    return true;
  }

  if (op == GOAL_PUTFLOOR)
  {
    x = *pTable++;
    y = *pTable++;
    GoalAdjustCel (rank, x, y);
    channel = *pTable++;
    icon    = *pTable++;
    if (icon == -2)
      icon = m_blupi[rank].vIcon;
    PutFloor (GetCel (x, y), channel, icon);
    return true;
  }

  if (op == GOAL_PUTOBJECT)
  {
    x = *pTable++;
    y = *pTable++;
    GoalAdjustCel (rank, x, y);
    channel = *pTable++;
    icon    = *pTable++;
    if (channel == -3 && icon == -3) // l'objet transporté ?
    {
      channel = m_blupi[rank].takeChannel;
      icon    = m_blupi[rank].takeIcon;
    }
    PutObject (GetCel (x, y), channel, icon);
    return true;
  }

  if (op == GOAL_BUILDFLOOR)
  {
    x = *pTable++;
    y = *pTable++;
    GoalAdjustCel (rank, x, y);
    cel.x = (x / 2) * 2;
    cel.y = (y / 2) * 2;
    GetFloor (cel, channel, i);
    channel  = *pTable++;
    icon     = *pTable++;
    mchannel = *pTable++;
    micon    = *pTable++;
    total    = *pTable++;
    delai    = *pTable++;
    step     = *pTable++;

    if (
      i >= 19 && i <= 32 && // herbe foncée ?
      icon == 57)
      icon = 58; // sol tomate foncé
    if (i == 58 && icon == 1)
      icon = 20; // remet herbe foncée

    if (!MoveCreate (
          cel, rank, true, channel, icon, mchannel, micon, total, delai, step))
      goto error;
    return true;
  }

  if (op == GOAL_BUILDOBJECT)
  {
    x = *pTable++;
    y = *pTable++;
    GoalAdjustCel (rank, x, y);
    cel.x    = (x / 2) * 2;
    cel.y    = (y / 2) * 2;
    channel  = *pTable++;
    icon     = *pTable++;
    mchannel = *pTable++;
    micon    = *pTable++;
    total    = *pTable++;
    delai    = *pTable++;
    step     = *pTable++;
    if (channel == -2 && icon == -2) // l'objet actuel ?
      GetObject (cel, channel, icon);
    ArrangeBuild (cel, channel, icon); // arrange les murs autour
    if (!MoveCreate (
          cel, rank, false, channel, icon, mchannel, micon, total, delai, step))
      goto error;
    return true;
  }

  if (op == GOAL_FINISHMOVE)
  {
    MoveFinish (rank);
    return true;
  }

  if (op == GOAL_ARRANGEOBJECT)
  {
    x = *pTable++;
    y = *pTable++;
    GoalAdjustCel (rank, x, y);
    MoveFinish (GetCel (x, y));
    ArrangeObject (GetCel (x, y));
    return true;
  }

  if (op == GOAL_EXPLOSE1)
  {
    x = *pTable++;
    y = *pTable++;
    GoalAdjustCel (rank, x, y);
    cel = GetCel (x, y);

    BlupiKill (rank, cel, 0);
    MoveFinish (cel);

    // Faut-il démarrer une explosion en chaîne.
    GetObject (cel, channel, icon);
    if (channel == CHOBJECT && icon == 85) // dynamite ?
    {
      rank = BlupiCreate (
        GetCel (cel, 1, 1), ACTION_STOP, DIRECT_E, 6,
        MAXENERGY); // crée un détonnateur
      if (rank >= 0)
      {
        GoalStart (rank, EV_ACTION_T_DYNAMITE, cel);
        m_blupi[rank].bCache = true;
      }
    }
    else
    {
      PutObject (cel, -1, -1); // supprime l'objet
      ArrangeObject (cel);
    }
    return true;
  }

  if (op == GOAL_EXPLOSE2)
  {
    x = *pTable++;
    y = *pTable++;
    GoalAdjustCel (rank, x, y);
    cel = GetCel ((x / 2) * 2, (y / 2) * 2);

    GetObject (cel, channel, icon);
    if (channel != CHOBJECT || icon != 85) // dynamite ?
    {
      channel = CHOBJECT;
      icon    = -1;
      ArrangeBuild (cel, channel, icon); // arrange les murs autour
      if (!MoveCreate (cel, rank, false, CHOBJECT, -1, -1, -1, 10, 1, -1 * 100))
        goto error;
      MoveAddIcons (cel, 6); // explosion
    }
    return true;
  }

  if (op == GOAL_ADDMOVES)
  {
    x = *pTable++;
    y = *pTable++;
    GoalAdjustCel (rank, x, y);
    icon = *pTable++;
    MoveAddMoves (GetCel (x, y), icon);
    return true;
  }

  if (op == GOAL_ADDICONS)
  {
    x = *pTable++;
    y = *pTable++;
    GoalAdjustCel (rank, x, y);
    icon = *pTable++;
    MoveAddIcons (GetCel (x, y), icon);
    return true;
  }

  if (op == GOAL_ACTION)
  {
    action = *pTable++;
    direct = *pTable++;
    BlupiInitAction (rank, action, direct);
    return true;
  }

  if (op == GOAL_ELECTRO)
  {
    x = *pTable++;
    y = *pTable++;
    GoalAdjustCel (rank, x, y);
    cel  = GetCel ((x / 2) * 2, (y / 2) * 2);
    icon = *pTable++;
    if (MoveCreate (
          cel, rank, true, CHFLOOR, -1, -1, -1, 100, 1, 100, false, true))
      MoveAddIcons (cel, icon);
    BlupiKill (rank, cel, 1);
    return true;
  }

  if (op == GOAL_MALADE)
  {
    m_blupi[rank].bMalade = *pTable++;
    return true;
  }

  if (op == GOAL_WORK)
  {
    cel.x                                   = m_blupi[rank].cel.x + (*pTable++);
    cel.y                                   = m_blupi[rank].cel.y + (*pTable++);
    m_decor[cel.x / 2][cel.y / 2].workBlupi = rank;
    return true;
  }

  if (op == GOAL_INTERRUPT)
  {
    m_blupi[rank].interrupt = *pTable++; // change le niveau
    return true;
  }

  if (op == GOAL_ENERGY)
  {
    if (m_blupi[rank].energy <= *pTable++)
      goto error;
    return true;
  }

  if (op == GOAL_ISNOMALADE)
  {
    if (m_blupi[rank].bMalade)
      goto error;
    return true;
  }

  if (op == GOAL_TAKE)
  {
    cel.x = m_blupi[rank].cel.x + (*pTable++);
    cel.y = m_blupi[rank].cel.y + (*pTable++);
    MoveFinish (rank);
    m_blupi[rank].takeChannel = m_decor[cel.x / 2][cel.y / 2].objectChannel;
    m_blupi[rank].takeIcon    = m_decor[cel.x / 2][cel.y / 2].objectIcon;
    m_decor[cel.x / 2][cel.y / 2].objectChannel = -1;
    m_decor[cel.x / 2][cel.y / 2].objectIcon    = -1;
    return true;
  }

  if (op == GOAL_TAKEOBJECT)
  {
    cel.x                     = m_blupi[rank].cel.x + (*pTable++);
    cel.y                     = m_blupi[rank].cel.y + (*pTable++);
    channel                   = *pTable++;
    icon                      = *pTable++;
    m_blupi[rank].takeChannel = channel;
    m_blupi[rank].takeIcon    = icon;
    return true;
  }

  if (op == GOAL_LABO)
  {
    m_blupi[rank].takeChannel = CHOBJECT;
    if (m_blupi[rank].takeIcon == 82) // fleurs normales ?
    {
      m_blupi[rank].takeIcon = 80; // bouteille
    }
    if (m_blupi[rank].takeIcon == 84) // fleurs foncées ?
    {
      m_blupi[rank].takeIcon = 85; // dynamite
    }
    if (m_blupi[rank].takeIcon == 95) // fleurs vertes ?
    {
      m_blupi[rank].takeIcon = 93; // piège
    }
    if (m_blupi[rank].takeIcon == 60) // tomates ?
    {
      m_blupi[rank].takeIcon = 92; // poison
    }
    return true;
  }

  if (op == GOAL_CACHE)
  {
    m_blupi[rank].bCache = *pTable++;
    if (
      *pTable++ &&                                               // bDynamite ?
      m_blupi[rank].perso == 0 && m_blupi[rank].vehicule == 3 && // armure ?
      !m_bInvincible)
      m_blupi[rank].bCache = false;
    return true;
  }

  if (op == GOAL_DELETE)
  {
    if (
      m_blupi[rank].perso == 0 &&    // blupi ?
      m_blupi[rank].vehicule == 3 && // armure ?
      !m_bInvincible)
      return true;
    BlupiDelete (rank); // snif ...
    return true;
  }

  if (op == GOAL_DEPOSE)
  {
    m_blupi[rank].takeChannel = -1;
    return true;
  }

  if (op == GOAL_NEWBLUPI)
  {
    cel.x = m_blupi[rank].cel.x + (*pTable++);
    cel.y = m_blupi[rank].cel.y + (*pTable++);
    PutObject (cel, -1, -1); // enlève les oeufs

    rank = BlupiCreate (cel, ACTION_STOP, DIRECT_E, 0, MAXENERGY / 4);
    if (rank >= 0)
    {
      m_blupi[rank].energy = MAXENERGY / 4;
      BlupiInitAction (rank, ACTION_BORN);
    }

    cel.x++;
    rank = BlupiCreate (cel, ACTION_STOP, DIRECT_E, 0, MAXENERGY / 4);
    if (rank >= 0)
    {
      m_blupi[rank].energy = MAXENERGY / 4;
      BlupiInitAction (rank, ACTION_BORN);
    }

    cel.x--;
    cel.y++;
    rank = BlupiCreate (cel, ACTION_STOP, DIRECT_E, 0, MAXENERGY / 4);
    if (rank >= 0)
    {
      m_blupi[rank].energy = MAXENERGY / 4;
      BlupiInitAction (rank, ACTION_BORN);
    }

    cel.x++;
    rank = BlupiCreate (cel, ACTION_STOP, DIRECT_E, 0, MAXENERGY / 4);
    if (rank >= 0)
    {
      m_blupi[rank].energy = MAXENERGY / 4;
      BlupiInitAction (rank, ACTION_BORN);
    }
    return true;
  }

  if (op == GOAL_NEWPERSO)
  {
    cel.x = m_blupi[rank].cel.x + (*pTable++);
    cel.y = m_blupi[rank].cel.y + (*pTable++);
    icon  = *pTable++;

    destCel = cel;
    destCel.x++;
    if (IsBlupiHereEx (destCel, rank, false)) // destination occupée ?
    {
      m_blupi[rank].goalPhase--; // on attend ...
      return true;
    }
    destCel.x++;
    if (IsBlupiHereEx (destCel, rank, false)) // destination occupée ?
    {
      destCel.y--;
      if (
        icon == 5 ||                          // bombe ?
        IsBlupiHereEx (destCel, rank, false)) // destination occupée ?
      {
        m_blupi[rank].goalPhase--; // on attend ...
        return true;
      }
    }

    rank = BlupiCreate (cel, ACTION_STOP, DIRECT_E, icon, MAXENERGY);
    if (rank >= 0)
      m_blupi[rank].goalCel = destCel;
    return true;
  }

  if (op == GOAL_USINEBUILD)
  {
    cel.x = m_blupi[rank].cel.x + (*pTable++);
    cel.y = m_blupi[rank].cel.y + (*pTable++);
    if (!IsUsineBuild (rank, cel))
      goto error;
    return true;
  }

  if (op == GOAL_USINEFREE)
  {
    cel.x = m_blupi[rank].cel.x + (*pTable++);
    cel.y = m_blupi[rank].cel.y + (*pTable++);
    if (!IsUsineFree (rank, cel))
    {
      GoalStop (rank, true);
      m_blupi[rank].goalCel = GetCel (cel, 1, -1); // à côté de la porte
      //?         m_blupi[rank].goalAction = 0;  // stoppe sitôt après
      //?         m_blupi[rank].interrupt = 1;
      //?         GoalUnwork(rank);
      //?         FlushUsed(rank);
    }
    return true;
  }

  if (op == GOAL_AMORCE)
  {
    cel.x = m_blupi[rank].cel.x + (*pTable++);
    cel.y = m_blupi[rank].cel.y + (*pTable++);
    if (IsBlupiHereEx (cel, rank, false))
      goto error;
    // Crée un détonnateur de mine (blupi invisible).
    rank = BlupiCreate (cel, ACTION_STOP, DIRECT_E, 6, MAXENERGY);
    if (rank >= 0)
    {
      m_blupi[rank].bCache     = true; // invisible
      m_blupi[rank].goalAction = EV_ACTION_MINE2;
      m_blupi[rank].goalPhase  = 0;
      m_blupi[rank].goalCel.x  = -1;
      m_blupi[rank].interrupt  = 1;
    }
    return true;
  }

  if (op == GOAL_VEHICULE)
  {
    m_blupi[rank].vehicule = *pTable++;
    if (
      m_blupi[rank].vehicule != 0 &&         // pas à pied ?
      m_blupi[rank].takeChannel != -1 &&     // porte qq chose ?
      m_blupi[rank].energy <= MAXENERGY / 4) // faible ?
      m_blupi[rank].energy = MAXENERGY / 4 + 1;
    return true;
  }

  if (op == GOAL_ACTUALISE)
  {
    BlupiActualise (rank);
    return true;
  }

  if (op == GOAL_SOUND)
  {
    icon = *pTable++;
    BlupiSound (rank, static_cast<Sounds> (icon), pos);
    return true;
  }

  if (op == GOAL_REPEAT)
  {
    icon                  = *pTable++;
    m_blupi[rank].bRepeat = icon;
    return true;
  }

  if (op == GOAL_OTHER)
  {
    if (!m_blupi[rank].bRepeat)
      goto term;

    // Bouton stop pressé ?
    if (m_blupi[rank].stop == 1)
      goto term;

    channel = *pTable++;
    first   = *pTable++;
    last    = *pTable++;
    first2  = *pTable++;
    last2   = *pTable++;
    action  = *pTable++;
    if (!SearchOtherObject (
          rank, m_blupi[rank].cel, action, 100, channel, first, last, first2,
          last2, m_blupi[rank].goalHili, icon))
      goto term;
    if (action == EV_ACTION_ABAT1 || action == EV_ACTION_ROC1)
    {
      action += icon - first; // EV_ACTION_ABAT1..6
    }
    m_blupi[rank].goalAction = action;
    m_blupi[rank].goalPhase  = 0;
    m_blupi[rank].goalCel.x  = -1;
    m_blupi[rank].interrupt  = 1;
    GoalInitJauge (rank);
    GoalUnwork (rank);
    FlushUsed (rank);
    return true;
  }

  if (op == GOAL_OTHERFIX)
  {
    if (!m_blupi[rank].bRepeat)
      goto term;

    // Bouton stop pressé ?
    if (m_blupi[rank].stop == 1)
      goto term;

    channel = *pTable++;
    first   = *pTable++;
    last    = *pTable++;
    first2  = *pTable++;
    last2   = *pTable++;
    action  = *pTable++;
    if (!SearchOtherObject (
          rank, m_blupi[rank].fix, action, 100, channel, first, last, first2,
          last2, m_blupi[rank].goalHili, icon))
      goto term;
    if (action == EV_ACTION_ABAT1 || action == EV_ACTION_ROC1)
    {
      action += icon - first; // EV_ACTION_ABAT1..6
    }
    m_blupi[rank].goalAction = action;
    m_blupi[rank].goalPhase  = 0;
    m_blupi[rank].goalCel.x  = -1;
    m_blupi[rank].interrupt  = 1;
    GoalInitJauge (rank);
    GoalUnwork (rank);
    FlushUsed (rank);
    return true;
  }

  if (op == GOAL_OTHERLOOP)
  {
    action = *pTable++;
    if (m_blupi[rank].cLoop < m_blupi[rank].nLoop)
    {
      m_blupi[rank].goalAction = action;
      m_blupi[rank].goalPhase  = 0;
      m_blupi[rank].goalCel.x  = -1;
      m_blupi[rank].interrupt  = 1;
      GoalInitJauge (rank);
      GoalUnwork (rank);
      FlushUsed (rank);
    }
    return true;
  }

  if (op == GOAL_NEXTLOOP)
  {
    m_blupi[rank].cLoop++;
    return true;
  }

  if (op == GOAL_FIX)
  {
    m_blupi[rank].fix.x = m_blupi[rank].cel.x + (*pTable++);
    m_blupi[rank].fix.y = m_blupi[rank].cel.y + (*pTable++);
    return true;
  }

  if (op == GOAL_FLOORJUMP)
  {
    channel = *pTable++;
    icon    = *pTable++;
    action  = *pTable++;
    GetFloor (m_blupi[rank].cel, mchannel, micon);
    if (channel == mchannel && icon == micon)
    {
      m_blupi[rank].goalAction = action;
      m_blupi[rank].goalPhase  = 0;
      m_blupi[rank].goalCel.x  = -1;
      m_blupi[rank].interrupt  = 1;
      GoalInitJauge (rank);
      GoalUnwork (rank);
      FlushUsed (rank);
    }
    return true;
  }

  if (op == GOAL_ADDDRAPEAU)
  {
    cel.x = m_blupi[rank].cel.x + (*pTable++);
    cel.y = m_blupi[rank].cel.y + (*pTable++);
    AddDrapeau (cel); // cellule sondée
    return true;
  }

  if (op == GOAL_TELEPORTE)
  {
    pos.x = *pTable++;
    pos.y = *pTable++;

    cMem = m_blupi[rank].cel;
    GetFloor (cMem, channel, icon);
    PutFloor (cMem, -1, -1);
    bOK = SearchOtherObject (
      rank, m_blupi[rank].cel, EV_ACTION_GO, 1000, CHFLOOR, 80, 80, -1, -1, cel,
      i);
    PutFloor (cMem, channel, icon);
    if (!bOK)
      goto error;

    cel.x += pos.x;
    cel.y += pos.y;
    if (IsBlupiHereEx (cel, rank, false) || !IsFreeCel (cel, rank))
      goto error;
    m_blupi[rank].cel = cel;
    BlupiPushFog (rank);
    if (m_blupi[rank].bHili)
      SetCorner (cel, true);
    return true;
  }

  if (op == GOAL_IFTERM)
  {
    cel.x = m_blupi[rank].cel.x + (*pTable++);
    cel.y = m_blupi[rank].cel.y + (*pTable++);
    if (!IsFreeCel (cel, rank) || IsBlupiHereEx (cel, rank, false))
      goto term;
    return true;
  }

  if (op == GOAL_IFDEBARQUE)
  {
    cel.x                  = m_blupi[rank].cel.x + (*pTable++);
    cel.y                  = m_blupi[rank].cel.y + (*pTable++);
    m_blupi[rank].vehicule = 0; // à pied
    bOK = IsFreeCel (cel, rank) && !IsBlupiHereEx (cel, rank, false);
    m_blupi[rank].vehicule = 1; // en bateau
    if (!bOK)
      goto term;
    return true;
  }

  if (op == GOAL_SKIPSKILL)
  {
    i     = (*pTable++);
    total = (*pTable++);
    if (m_skill == i)
    {
      m_blupi[rank].goalPhase += total; // saute qq instructions
    }
    return true;
  }

  if (op == GOAL_TERM)
  {
  term:
    bError = false;
  }

  if (op == GOAL_WAITFREE)
  {
    cel.x = m_blupi[rank].cel.x + (*pTable++);
    cel.y = m_blupi[rank].cel.y + (*pTable++);
    if (IsBlupiHereEx (cel, rank, false)) // destination occupée ?
    {
      m_blupi[rank].goalPhase--; // on attend ...

      rank = m_blupiHere;
      if (
        m_blupi[rank].goalAction != EV_ACTION_GO &&
        m_blupi[rank].goalAction != EV_ACTION_ELECTRO &&
        m_blupi[rank].goalAction != EV_ACTION_ELECTROm)
      {
        destCel.x = cel.x;
        destCel.y = cel.y - 1;
        if (!IsBlupiHereEx (destCel, rank, false))
        {
          GoalStart (rank, EV_ACTION_GO, destCel);
          return true;
        }

        destCel.x = cel.x + 1;
        destCel.y = cel.y;
        if (!IsBlupiHereEx (destCel, rank, false))
        {
          GoalStart (rank, EV_ACTION_GO, destCel);
          return true;
        }

        destCel.x = cel.x;
        destCel.y = cel.y + 1;
        if (!IsBlupiHereEx (destCel, rank, false))
        {
          GoalStart (rank, EV_ACTION_GO, destCel);
          return true;
        }

        destCel.x = cel.x + 1;
        destCel.y = cel.y - 1;
        if (!IsBlupiHereEx (destCel, rank, false))
        {
          GoalStart (rank, EV_ACTION_GO, destCel);
          return true;
        }

        destCel.x = cel.x + 1;
        destCel.y = cel.y + 1;
        if (!IsBlupiHereEx (destCel, rank, false))
        {
          GoalStart (rank, EV_ACTION_GO, destCel);
          return true;
        }

        if (m_blupi[rank].perso == 0)
        {
          if (m_blupi[rank].bMalade)
            action = EV_ACTION_ELECTROm;
          else
            action = EV_ACTION_ELECTRO;
          GoalStart (rank, action, m_blupi[rank].cel);
          return true;
        }
      }
    }
    return true;
  }

error:
  i = m_blupi[rank].repeatLevel;
  GoalStop (rank, bError, i == -1);
  if (i != -1) // répétition en cours ?
  {
    button = static_cast<Buttons> (m_blupi[rank].listButton[i]);
    cMem   = m_blupi[rank].listCel[i];
    param  = m_blupi[rank].listParam[i];
    cel    = cMem;
    if (RepeatAdjust (rank, button, cel, cMem, param, i))
    {
      if (IsBlupiHereEx (cel, rank, false)) // destination occupée ?
      {
        m_blupi[rank].repeatLevel = i;                     // on continue ...
        GoalStart (rank, EV_ACTION_GO, m_blupi[rank].cel); // on attend ...
        return true;
      }
      if (BlupiGoal (rank, button, cel, cMem))
      {
        m_blupi[rank].repeatLevel = i; // on continue ...
        return true;
      }
    }
  }
  return false;
}

// Supprime le blocage de la cellule dans laquelle
// blupi travaille.

void
CDecor::GoalUnwork (Sint32 rank)
{
  Sint32 x, y;

  for (x = 0; x < MAXCELX / 2; x++)
  {
    for (y = 0; y < MAXCELY / 2; y++)
    {
      if (m_decor[x][y].workBlupi == rank)
      {
        m_decor[x][y].workBlupi = -1; // débloque
      }
    }
  }
}

// Stoppe complètement une action.

void
CDecor::GoalStop (Sint32 rank, bool bError, bool bSound)
{
  Point pos;

  static Sounds table_sound_term[] = {
    SOUND_TERM1, SOUND_TERM2, SOUND_TERM3,
    SOUND_TERM4, SOUND_TERM5, SOUND_TERM6,
  };

  static Sounds table_sound_boing[] = {
    SOUND_BOING1, SOUND_BOING2, SOUND_BOING3,
  };

  if (bError && bSound)
  {
    ListRemove (rank); // supprime la dernière action mémorisée
  }

  m_blupi[rank].goalAction  = 0;
  m_blupi[rank].goalPhase   = 0;
  m_blupi[rank].goalCel.x   = -1;
  m_blupi[rank].jaugePhase  = -1;
  m_blupi[rank].jaugeMax    = -1;
  m_blupi[rank].interrupt   = 1; // remet le niveau normal
  m_blupi[rank].busyCount   = 0;
  m_blupi[rank].busyDelay   = 0;
  m_blupi[rank].repeatLevel = -1; // stoppe la répétition
  FlushUsed (rank);
  MoveFinish (rank);
  GoalUnwork (rank);

  // En cas d'erreur, il faut accepter de traverser la
  // construction avortée (par exemple, le massif d'arbres
  // dans lequel blupi se trouve, mais qui n'a pas pu
  // être abattu).
  if (!bError)
    m_blupi[rank].passCel.x = -1;

  m_blupi[rank].stop = 0; // relâche bouton stop

  if (
    bSound && (m_blupi[rank].perso == 0 || // blupi ?
               m_blupi[rank].perso == 8))  // assistant ?
  {
    if (bError)
    {
      pos.x = LXIMAGE () / 2;
      pos.y = LYIMAGE () / 2;
      BlupiSound (
        rank, table_sound_boing[Random (0, countof (table_sound_boing) - 1)],
        pos, true);
    }
    else
    {
      pos = ConvCelToPos (m_blupi[rank].cel);
      BlupiSound (
        rank, table_sound_term[Random (0, countof (table_sound_term) - 1)], pos,
        true);
    }
  }
}

// Teste si une cellule est déjà utilisée comme but pour
// n'importe quel blupi.

bool
CDecor::BlupiIsGoalUsed (Point cel)
{
  Sint32 rank;

  for (rank = 0; rank < MAXBLUPI; rank++)
  {
    if (
      m_blupi[rank].bExist && m_blupi[rank].goalCel.x / 2 == cel.x / 2 &&
      m_blupi[rank].goalCel.y / 2 == cel.y / 2)
      return true;
  }

  return false;
}

// Démarre ou stoppe un rayon entre deux tours.

void
CDecor::BlupiStartStopRayon (Sint32 rank, Point startCel, Point endCel)
{
  Sint32 i, icon, icon2;
  Point  cel, cel2, vector, pos;

  if (
    m_blupi[rank].perso == 1 || // araignée ?
    m_blupi[rank].perso == 2)   // virus ?
    return;

  // Stoppe un rayon.
  cel.x = (endCel.x / 2) * 2;
  cel.y = (endCel.y / 2) * 2;
  icon  = m_decor[cel.x / 2][cel.y / 2].objectIcon;
  if ((icon == 10000 || icon == 10001) && MoveIsUsed (cel))
  {
    if (MoveIsUsed (cel))
    {
      MoveFinish (cel);

      pos = ConvCelToPos (cel);
      BlupiSound (rank, SOUND_RAYON2, pos);
    }

    for (i = 0; i < 4; i++)
    {
      vector = GetVector (i * 2 * 16);
      cel.x  = (endCel.x / 2) * 2 + vector.x * 2;
      cel.y  = (endCel.y / 2) * 2 + vector.y * 2;
      icon   = m_decor[cel.x / 2][cel.y / 2].objectIcon;
      if (icon == 10000 || icon == 10001)
        MoveFinish (cel);
    }
  }

  // Démarre un rayon.
  cel.x  = (startCel.x / 2) * 2;
  cel.y  = (startCel.y / 2) * 2;
  cel2.x = (endCel.x / 2) * 2;
  cel2.y = (endCel.y / 2) * 2;
  icon   = m_decor[cel.x / 2][cel.y / 2].objectIcon;
  icon2  = m_decor[cel2.x / 2][cel2.y / 2].objectIcon;
  if ((icon == 10000 || icon == 10001) && icon2 != 10000 && icon2 != 10001)
  {
    if (MoveCreate (cel, -1, false, CHOBJECT, -1, -1, -1, 9999, 1, 0, true))
    {
      MoveAddIcons (cel, icon == 10000 ? 4 : 5, true); // éclairs
    }

    pos = ConvCelToPos (cel);
    BlupiSound (rank, SOUND_RAYON1, pos);

    for (i = 0; i < 4; i++)
    {
      vector = GetVector (i * 2 * 16);
      cel.x  = (startCel.x / 2) * 2 + vector.x * 2;
      cel.y  = (startCel.y / 2) * 2 + vector.y * 2;
      icon   = m_decor[cel.x / 2][cel.y / 2].objectIcon;
      if (icon == 10000 || icon == 10001)
      {
        if (MoveCreate (cel, -1, false, CHOBJECT, -1, -1, -1, 9999, 1, 0, true))
        {
          MoveAddIcons (cel, icon == 10000 ? 4 : 5, true); // éclairs
        }
      }
    }
  }
}

// Tourne un blupi, si nécessaire.
// Retourne false si ce n'est pas nécessaire.

bool
CDecor::BlupiRotate (Sint32 rank)
{
  Sint32 aDirect, sDirect, ip, in, sens = 0;
  bool   bOK;
  Point  pos;

  aDirect = m_blupi[rank].aDirect;
  sDirect = m_blupi[rank].sDirect;

  if (aDirect == sDirect)
    return false;

  if (sDirect > aDirect)
    ip = sDirect + 0 * 16 - aDirect;
  else
    ip = sDirect + 8 * 16 - aDirect;

  if (aDirect > sDirect)
    in = aDirect + 0 * 16 - sDirect;
  else
    in = aDirect + 8 * 16 - sDirect;

  if (ip == 0 || in == 0)
  {
    m_blupi[rank].aDirect = m_blupi[rank].sDirect;
    return false;
  }

  if (
    m_blupi[rank].perso == 0 &&  // blupi ?
    m_blupi[rank].vehicule == 1) // en bateau ?
  {
    if (ip == in)
      sens = Random (0, 1) ? 8 : 8 * 16 - 8;
    if (ip < in)
      sens = 8;
    if (ip > in)
      sens = 8 * 16 - 8;
    aDirect = (aDirect + sens) % (8 * 16);
  }
  else if (
    m_blupi[rank].perso == 0 &&  // blupi ?
    m_blupi[rank].vehicule == 2) // en jeep ?
  {
    if (ip == in)
      sens = Random (0, 1) ? 8 : 8 * 16 - 8;
    if (ip < in)
      sens = 8;
    if (ip > in)
      sens = 8 * 16 - 8;
    aDirect = (aDirect + sens) % (8 * 16);
  }
  else if (
    m_blupi[rank].perso == 0 &&  // blupi ?
    m_blupi[rank].vehicule == 3) // armure ?
  {
    if (ip == in)
      sens = Random (0, 1) ? 4 : 8 * 16 - 4;
    if (ip < in)
      sens = 4;
    if (ip > in)
      sens = 8 * 16 - 4;
    aDirect = (aDirect + sens) % (8 * 16);
  }
  else if (m_blupi[rank].perso == 3) // tracks ?
  {
    if (ip == in)
      sens = Random (0, 1) ? 4 : 8 * 16 - 4;
    if (ip < in)
      sens = 4;
    if (ip > in)
      sens = 8 * 16 - 4;
    aDirect = (aDirect + sens) % (8 * 16);
  }
  else if (m_blupi[rank].perso == 4) // robot ?
  {
    pos = ConvCelToPos (m_blupi[rank].cel);
    BlupiSound (rank, SOUND_R_ROTATE, pos);

    if (ip == in)
      sens = Random (0, 1) ? 2 : 8 * 16 - 2;
    if (ip < in)
      sens = 2;
    if (ip > in)
      sens = 8 * 16 - 2;
    aDirect = (aDirect + sens) % (8 * 16);
  }
  else if (m_blupi[rank].perso == 8) // disciple ?
  {
    if (ip == in)
      sens = Random (0, 1) ? 8 : 8 * 16 - 8;
    if (ip < in)
      sens = 8;
    if (ip > in)
      sens = 8 * 16 - 8;
    aDirect = (aDirect + sens) % (8 * 16);
  }
  else
  {
    if (ip == in)
      sens = Random (0, 1) ? 1 : 7;
    if (ip < in)
      sens = 1;
    if (ip > in)
      sens = 7;
    aDirect = ((aDirect / 16 + sens) % 8) * 16;
  }

  m_blupi[rank].lastIcon = m_blupi[rank].icon;
  bOK                    = Rotate (m_blupi[rank].icon, aDirect);
  if (bOK)
  {
    m_blupi[rank].aDirect = aDirect;
    return true;
  }
  else
  {
    m_blupi[rank].aDirect = m_blupi[rank].sDirect;
    return false;
  }
}

// Avance un blupi existant.

bool
CDecor::BlupiNextAction (Sint32 rank)
{
  bool   bOK;
  Point  pos, iCel;
  Sint32 a, min;
  Sounds sound;

  if (!m_blupi[rank].bExist)
    return false;

  if (!g_restoreBugs)
  {
    /* Check if a Blupi is already doing a conflicting action at the same place.
     * It happens for example when a blupi wants to carry an object from a
     * direction and a second from the other direction. Without this check, the
     * object is duplicated.
     */
    for (int i = 0; i < MAXBLUPI; ++i)
    {
      if (rank == i || !m_blupi[i].bExist)
        continue;

      if (
        (m_blupi[rank].goalAction == EV_ACTION_CARRY ||
         m_blupi[rank].goalAction == EV_ACTION_EAT ||
         m_blupi[rank].goalAction == EV_ACTION_DRINK) &&
        (m_blupi[i].goalAction == EV_ACTION_CARRY2 ||
         m_blupi[i].goalAction == EV_ACTION_EAT2 ||
         m_blupi[i].goalAction == EV_ACTION_DRINK2) &&
        m_blupi[rank].goalHili.x == m_blupi[i].goalHili.x &&
        m_blupi[rank].goalHili.y == m_blupi[i].goalHili.y)
      {
        BlupiInitAction (i, ACTION_STOP);
        GoalStop (i, true);
        return false;
      }
    }

    /* Prevent Blupi to take a trap when an enemy is already captured. */
    if (m_blupi[rank].perso == 0 && m_blupi[rank].action == ACTION_CARRY)
    {
      Sint32 ch, icon;

      auto exists = this->GetObject (m_blupi[rank].goalHili, ch, icon);
      if (exists && ch == CHOBJECT)
      {
        switch (icon)
        {
        case 96: // spider in trap
        case 97: // track in trap
        case 98: // robot in trap
          BlupiInitAction (rank, ACTION_STOP);
          GoalStop (rank, true);
          return false;

        default:
          break;
        }
      }
    }
  }

  if (m_blupi[rank].clicDelay > 0)
    m_blupi[rank].clicDelay--;
  if (m_blupi[rank].clicDelay == 0)
    m_blupi[rank].clicCount = 0;

  bOK = true;
  if (!BlupiRotate (rank)) // si rotation pas nécessaire
  {
    m_blupi[rank].lastIcon = m_blupi[rank].icon;

    bOK = Action (
      m_blupi[rank].action, m_blupi[rank].aDirect, m_blupi[rank].phase,
      m_blupi[rank].step, m_blupi[rank].channel, m_blupi[rank].icon,
      m_blupi[rank].pos, m_blupi[rank].posZ, sound);
    BlupiAdaptIcon (rank);

    if (sound != SOUND_NONE)
    {
      pos = ConvCelToPos (m_blupi[rank].cel);
      BlupiSound (rank, sound, pos);
    }
  }

  a = GetAmplitude (m_blupi[rank].action);

  iCel = m_blupi[rank].cel;

  if (
    m_blupi[rank].pos.x == (DIMCELX / 2) * a &&
    m_blupi[rank].pos.y == (DIMCELY / 2) * a)
  {
    m_blupi[rank].cel.x += a;
    BlupiPushFog (rank);
    BlupiStartStopRayon (rank, iCel, m_blupi[rank].cel);
  }

  if (
    m_blupi[rank].pos.x == -(DIMCELX / 2) * a &&
    m_blupi[rank].pos.y == -(DIMCELY / 2) * a)
  {
    m_blupi[rank].cel.x -= a;
    BlupiPushFog (rank);
    BlupiStartStopRayon (rank, iCel, m_blupi[rank].cel);
  }

  if (
    m_blupi[rank].pos.x == -(DIMCELX / 2) * a &&
    m_blupi[rank].pos.y == (DIMCELY / 2) * a)
  {
    m_blupi[rank].cel.y += a;
    BlupiPushFog (rank);
    BlupiStartStopRayon (rank, iCel, m_blupi[rank].cel);
  }

  if (
    m_blupi[rank].pos.x == (DIMCELX / 2) * a &&
    m_blupi[rank].pos.y == -(DIMCELY / 2) * a)
  {
    m_blupi[rank].cel.y -= a;
    BlupiPushFog (rank);
    BlupiStartStopRayon (rank, iCel, m_blupi[rank].cel);
  }

  if (m_blupi[rank].pos.x == 0 && m_blupi[rank].pos.y == DIMCELY * a)
  {
    m_blupi[rank].cel.x += a;
    m_blupi[rank].cel.y += a;
    BlupiPushFog (rank);
    BlupiStartStopRayon (rank, iCel, m_blupi[rank].cel);
  }

  if (m_blupi[rank].pos.x == 0 && m_blupi[rank].pos.y == -DIMCELY * a)
  {
    m_blupi[rank].cel.x -= a;
    m_blupi[rank].cel.y -= a;
    BlupiPushFog (rank);
    BlupiStartStopRayon (rank, iCel, m_blupi[rank].cel);
  }

  if (m_blupi[rank].pos.x == DIMCELX * a && m_blupi[rank].pos.y == 0)
  {
    m_blupi[rank].cel.x += a;
    m_blupi[rank].cel.y -= a;
    BlupiPushFog (rank);
    BlupiStartStopRayon (rank, iCel, m_blupi[rank].cel);
  }

  if (m_blupi[rank].pos.x == -DIMCELX * a && m_blupi[rank].pos.y == 0)
  {
    m_blupi[rank].cel.x -= a;
    m_blupi[rank].cel.y += a;
    BlupiPushFog (rank);
    BlupiStartStopRayon (rank, iCel, m_blupi[rank].cel);
  }

  // Blupi perd de l'énergie s'il fait qq chose.
  if (
    m_blupi[rank].action != ACTION_STOP &&
    m_blupi[rank].action != ACTION_STOPTIRED &&
    m_blupi[rank].action != ACTION_STOPb &&
    m_blupi[rank].action != ACTION_STOPJEEP &&
    m_blupi[rank].action != ACTION_MISC1 &&
    m_blupi[rank].action != ACTION_MISC2 &&
    m_blupi[rank].action != ACTION_MISC3 &&
    m_blupi[rank].action != ACTION_MISC4 &&
    m_blupi[rank].action != ACTION_MISC5 &&
    m_blupi[rank].action != ACTION_MISC6 &&
    m_blupi[rank].action != ACTION_MISC1f)
  {
    if (
      m_blupi[rank].energy > 0 && m_blupi[rank].perso == 0 && // blupi ?
      m_blupi[rank].vehicule == 0)                            // à pied ?
    {
      if (m_bSuper)
        a = 0;
      else
        a = 1;
      min = 0;
      if (
        m_blupi[rank].action != ACTION_WALK &&
        (m_blupi[rank].goalAction == EV_ACTION_WALL ||
         m_blupi[rank].goalAction == EV_ACTION_TOWER))
      {
        a   = 5;
        min = 1;
      }
      if (m_blupi[rank].action == ACTION_SLIDE)
      {
        if (m_bSuper)
          a = 0;
        else
          a = 40;
      }
      m_blupi[rank].energy -= a; // blupi se fatigue +/-
      if (m_blupi[rank].energy < min)
        m_blupi[rank].energy = min;
    }
  }

  // Blupi prend de l'énergie s'il mange.
  if (m_blupi[rank].action == ACTION_EAT)
  {
    if (m_blupi[rank].energy < MAXENERGY)
      m_blupi[rank].energy += MAXENERGY / (40 * 3);
  }

  // Le robot perd de l'énergie s'il fait qq chose.
  if (m_blupi[rank].action != ACTION_R_STOP)
  {
    if (m_blupi[rank].energy > 0 && m_blupi[rank].perso == 4)
    {
      m_blupi[rank].energy -= 3; // le robot se fatigue
      if (m_blupi[rank].energy < 1)
        m_blupi[rank].energy = 1;
    }
  }

  // Le robot prend de l'énergie s'il se recharge.
  if (m_blupi[rank].action == ACTION_R_LOAD)
  {
    if (m_blupi[rank].energy < MAXENERGY)
      m_blupi[rank].energy = MAXENERGY;
  }

  // Blupi guérrit s'il boit.
  if (m_blupi[rank].action == ACTION_DRINK)
  {
    m_blupi[rank].bMalade = false;
    if (m_blupi[rank].energy < MAXENERGY)
      m_blupi[rank].energy += MAXENERGY / (40 * 3);
  }

  // Si blupi est presque complètement épuisé, il stoppe.
  if (
    !bOK && m_blupi[rank].perso == 0 && m_blupi[rank].energy < 50 &&
    m_blupi[rank].energy != 0 && m_blupi[rank].action == ACTION_WALKTIRED)
  {
    BlupiInitAction (rank, ACTION_STOP);
    GoalStop (rank, true);
  }

  return bOK;
}

// Action suivante pour un blupi existant.

void
CDecor::BlupiNextGoal (Sint32 rank)
{
  Sint32 direct, action, channel, icon, min, lg, fRank, i;
  Point  pos, cel, vector;

  if (!m_blupi[rank].bExist)
    return;

  pos = ConvCelToPos (m_blupi[rank].cel);

  // Si blupi termine une action "mort", il doit disparaître.
  if (
    m_blupi[rank].action == ACTION_BURN ||
    m_blupi[rank].action == ACTION_TCHAO ||
    m_blupi[rank].action == ACTION_S_GRILL ||
    m_blupi[rank].action == ACTION_V_GRILL)
  {
    BlupiDelete (rank); // snif ...
    return;
  }

  // Si blupi passe trop prêt du feu, aie aie aie ...
  if (IsFireCel (m_blupi[rank].cel)) // blupi se brule les ailes ?
  {
    if (
      m_blupi[rank].perso == 0 && m_blupi[rank].vehicule != 3 && // pas armure ?
      !m_bInvincible && m_blupi[rank].goalAction != EV_ACTION_GRILLE)
    {
      BlupiDeselect (rank);
      GoalStart (rank, EV_ACTION_GRILLE, m_blupi[rank].cel);
      goto goal;
      //?         BlupiInitAction(rank, ACTION_BRULE);
      //?         goto init;
    }
    if (
      m_blupi[rank].perso == 1 || // araignée ?
      m_blupi[rank].perso == 2)   // virus ?
    {
      BlupiDelete (rank); // la bestiole meurt
      return;
    }
  }

  // Si blupi passe trop prêt d'un virus ...
  if (
    m_blupi[rank].perso == 0 &&
    m_blupi[rank].vehicule != 1 && // pas en bateau ?
    m_blupi[rank].vehicule != 3 && // pas armure ?
    !m_blupi[rank].bMalade &&      // en bonne santé ?
    m_blupi[rank].goalAction != EV_ACTION_GRILLE &&
    m_blupi[rank].goalAction != EV_ACTION_ELECTRO &&
    m_blupi[rank].goalAction != EV_ACTION_ELECTROm &&
    m_blupi[rank].goalAction != EV_ACTION_BOATDE &&
    m_blupi[rank].goalAction != EV_ACTION_BOATDS &&
    m_blupi[rank].goalAction != EV_ACTION_BOATDO &&
    m_blupi[rank].goalAction != EV_ACTION_BOATDN &&
    m_blupi[rank].goalAction != EV_ACTION_BOATAE &&
    m_blupi[rank].goalAction != EV_ACTION_BOATAS &&
    m_blupi[rank].goalAction != EV_ACTION_BOATAO &&
    m_blupi[rank].goalAction != EV_ACTION_BOATAN && !m_bInvincible &&
    IsVirusCel (m_blupi[rank].cel)) // blupi chope un virus ?
  {
    m_blupi[rank].bMalade = true;

    if (m_blupi[rank].energy > MAXENERGY / 4)
      m_blupi[rank].energy = MAXENERGY / 4;

    BlupiSound (rank, SOUND_VIRUS, pos);

    if (m_blupi[rank].vehicule == 2) // en jeep ?
    {
      GoalStart (rank, EV_ACTION_DJEEP, m_blupi[rank].cel);
      goto goal;
    }
  }

  // Si blupi est complètement épuisé, il meurt.
  if (m_blupi[rank].perso == 0 && m_blupi[rank].energy == 0)
  {
    BlupiDeselect (rank);
    BlupiInitAction (rank, ACTION_TCHAO);
    goto init;
  }

  // Assigne un but s'il s'agit d'une araignée.
  if (
    m_blupi[rank].perso == 1 && // araignée ?
    m_blupi[rank].goalAction != EV_ACTION_A_MORT)
  {
    cel   = m_blupi[rank].cel;
    cel.x = (cel.x / 2) * 2;
    cel.y = (cel.y / 2) * 2;
    GetObject (cel, channel, icon);
    if (
      channel == CHOBJECT && (icon == 10000 || icon == 10001) && // rayon ?
      MoveIsUsed (cel))                                          // enclenché ?
    {
      BlupiInitAction (rank, ACTION_S_GRILL);
      goto init;
    }

    cel = m_blupi[rank].cel;
    if (cel.x % 2 != 0 && cel.y % 2 != 0)
    {
      cel.x = (cel.x / 2) * 2;
      cel.y = (cel.y / 2) * 2;
      GetObject (cel, channel, icon);
      if (channel == CHOBJECT && icon == 60) // tomates ?
      {
        PutObject (cel, -1, -1); // plus de tomates
        BlupiSound (rank, SOUND_S_HIHI, pos);
      }
      if (channel == CHOBJECT && icon == 92) // poison ?
      {
        PutObject (cel, -1, -1); // plus de poison
        BlupiInitAction (rank, ACTION_STOP);
        GoalStart (rank, EV_ACTION_A_MORT, m_blupi[rank].cel);
        m_blupi[rank].goalCel = m_blupi[rank].cel;
        goto goal;
        //?             BlupiSound(rank, SOUND_A_POISON, pos);
        //?             BlupiInitAction(rank, ACTION_A_POISON);
        //?             goto init;
      }
      if (channel == CHOBJECT && icon == 93) // piège ?
      {
        BlupiSound (rank, SOUND_TRAP, pos);
        PutObject (cel, CHOBJECT, 96); // araignée piégée
        BlupiDelete (rank);            // supprime araignée
        return;
      }
    }

    m_blupi[rank].bExist = false;
    if (
      m_time % 5 == rank % 5 && // pas trop souvent !
      SearchSpiderObject (rank, m_blupi[rank].cel, 100, cel, icon))
    {
      m_blupi[rank].goalCel = cel;
      FlushUsed (rank);
      //          direct = DirectSearch(m_blupi[rank].cel, cel);
      //          if ( direct != -1 )
      //          {
      //              vector = GetVector(direct);
      //              m_blupi[rank].goalCel.x = m_blupi[rank].cel.x + vector.x;
      //              m_blupi[rank].goalCel.y = m_blupi[rank].cel.y + vector.y;
      //              FlushUsed(rank);
      //          }
    }
    m_blupi[rank].bExist = true;
  }

  // Assigne un but s'il s'agit d'un virus.
  if (m_blupi[rank].perso == 2) // virus ?
  {
    cel   = m_blupi[rank].cel;
    cel.x = (cel.x / 2) * 2;
    cel.y = (cel.y / 2) * 2;
    GetObject (cel, channel, icon);
    if (
      channel == CHOBJECT && (icon == 10000 || icon == 10001) && // rayon ?
      MoveIsUsed (cel))                                          // enclenché ?
    {
      BlupiInitAction (rank, ACTION_V_GRILL);
      goto init;
    }

    min   = 50; // ignore si trop loin !
    fRank = -1;
    for (i = 0; i < MAXBLUPI; i++)
    {
      if (m_blupi[i].bExist && m_blupi[i].perso == 0 && !m_blupi[i].bMalade)
      {
        lg = abs (m_blupi[rank].cel.x - m_blupi[i].cel.x) +
             abs (m_blupi[rank].cel.y - m_blupi[i].cel.y);
        if (lg < min)
        {
          min   = lg;
          fRank = i;
        }
      }
    }
    if (fRank != -1)
    {
      direct = DirectSearch (m_blupi[rank].cel, m_blupi[fRank].cel);
      if (direct != -1)
      {
        vector                  = GetVector (direct);
        m_blupi[rank].goalCel.x = m_blupi[rank].cel.x + vector.x;
        m_blupi[rank].goalCel.y = m_blupi[rank].cel.y + vector.y;
        FlushUsed (rank);
      }
    }
  }

  // Assigne un but s'il s'agit d'un tracks.
  if (
    m_blupi[rank].perso == 3 && // tracks ?
    m_blupi[rank].goalAction != EV_ACTION_T_DYNAMITE)
  {
    cel = m_blupi[rank].cel;
    if (cel.x % 2 != 0 && cel.y % 2 != 0)
    {
      cel.x = (cel.x / 2) * 2;
      cel.y = (cel.y / 2) * 2;
      GetObject (cel, channel, icon);
      if (channel == CHOBJECT && IsTracksObject (icon))
      {
        if (icon == 85) // dynamite ?
        {
          BlupiInitAction (rank, ACTION_STOP);
          GoalStart (rank, EV_ACTION_T_DYNAMITE, cel);
          goto goal;
        }
        if (
          icon == 125 || // mine ?
          icon == 127)
        {
          // Supprime le détonnateur.
          BlupiDelete (GetCel (cel.x + 1, cel.y + 1), 6);
        }
        if (icon == 93) // piège ?
        {
          BlupiSound (rank, SOUND_TRAP, pos);
          PutObject (cel, CHOBJECT, 97); // tracks piégé
          BlupiDelete (rank);            // supprime tracks
          return;
        }

        PutObject (cel, -1, -1); // plus d'objet

        BlupiSound (rank, SOUND_T_ECRASE, pos);
        BlupiInitAction (rank, ACTION_T_CRUSHED);
        goto init;
      }
    }
    cel                  = m_blupi[rank].cel;
    m_blupi[rank].bExist = false;
    if (
      IsBlupiHere (cel, false) && m_blupi[m_blupiHere].perso == 0 &&
      m_blupi[m_blupiHere].vehicule == 0) // à pied ?
    {
      m_blupi[rank].bExist = true;
      // Blupi écrasé au sol.
      if (MoveCreate (
            cel, rank, true, CHFLOOR, -1, -1, -1, 100, 1, 100, false, true))
      {
        if (m_blupi[m_blupiHere].bMalade)
          MoveAddIcons (cel, 10);
        else
          MoveAddIcons (cel, 9);
      }
      BlupiDelete (m_blupiHere); // plus de blupi !
      BlupiSound (rank, SOUND_AIE, pos);
      BlupiInitAction (rank, ACTION_T_CRUSHED); // écrase blupi
      goto init;
    }
    m_blupi[rank].bExist = true;

    //      if ( m_blupi[rank].goalCel.x != -1 )
    //      {
    //          GetObject(m_blupi[rank].goalCel, channel, icon);
    //          if ( IsTracksObject(icon) )  goto action;
    //      }
    m_blupi[rank].bExist = false;
    if (
      m_time % 5 == rank % 5 && // pas trop souvent !
      SearchTracksObject (rank, m_blupi[rank].cel, 25, cel, icon))
    {
      m_blupi[rank].goalCel = cel;
      FlushUsed (rank);
    }
    m_blupi[rank].bExist = true;
  }

  // Assigne un but s'il s'agit d'un robot.
  if (
    m_blupi[rank].perso == 4 && // robot ?
    m_blupi[rank].goalAction != EV_ACTION_T_DYNAMITE)
  {
    cel = m_blupi[rank].cel;
    if (cel.x % 2 != 0 && cel.y % 2 != 0)
    {
      cel.x = (cel.x / 2) * 2;
      cel.y = (cel.y / 2) * 2;
      GetObject (cel, channel, icon);
      if (channel == CHOBJECT && IsRobotObject (icon))
      {
        if (icon == 85) // dynamite ?
        {
          BlupiInitAction (rank, ACTION_STOP);
          GoalStart (rank, EV_ACTION_T_DYNAMITE, cel);
          goto goal;
        }
        if (
          icon == 125 || // mine ?
          icon == 127)
        {
          // Supprime le détonnateur.
          BlupiDelete (GetCel (cel.x + 1, cel.y + 1), 6);
        }
        if (icon == 93) // piège ?
        {
          BlupiSound (rank, SOUND_TRAP, pos);
          PutObject (cel, CHOBJECT, 98); // robot piégé
          BlupiDelete (rank);            // supprime robot
          return;
        }

        PutObject (cel, -1, -1); // plus d'objet

        BlupiSound (rank, SOUND_T_ECRASE, pos);
        BlupiInitAction (rank, ACTION_R_CRUSHED);
        goto init;
      }
    }
    cel = m_blupi[rank].cel;
    if (
      m_blupi[rank].goalAction == 0 &&
      m_time % 17 == rank % 17 && // pas trop souvent !
      SearchRobotObject (rank, m_blupi[rank].fix, 50, cel, icon, action))
    {
      if (action == -1)
      {
        m_blupi[rank].goalCel = cel;
        FlushUsed (rank);
      }
      else
      {
        BlupiInitAction (rank, ACTION_STOP);
        GoalStart (rank, action, cel);
        goto goal;
      }
    }
  }

  // Assigne un but s'il s'agit d'une bombe.
  if (
    m_blupi[rank].perso == 5 && // bombe ?
    m_blupi[rank].goalAction != EV_ACTION_T_DYNAMITE)
  {
    cel = m_blupi[rank].cel;
    if (cel.x % 2 != 0 && cel.y % 2 != 0)
    {
      cel.x = (cel.x / 2) * 2;
      cel.y = (cel.y / 2) * 2;
      GetObject (cel, channel, icon);
      if (channel == CHOBJECT && icon == 93) // piège ?
      {
        BlupiSound (rank, SOUND_TRAP, pos);
        PutObject (cel, CHOBJECT, 114); // bombe piégée
        BlupiDelete (rank);             // supprime bombe
        return;
      }
    }
    for (i = 0; i < 4; i++)
    {
      vector = GetVector (i * 2 * 16);
      cel.x  = ((m_blupi[rank].cel.x + vector.x * 2) / 2) * 2;
      cel.y  = ((m_blupi[rank].cel.y + vector.y * 2) / 2) * 2;
      GetObject (cel, channel, icon);
      if (
        channel == CHOBJECT &&
        IsBombeObject (icon) && // cabane, palissade, etc. ?
        icon != 93)             // pas piège ?
      {
        BlupiInitAction (rank, ACTION_STOP);
        GoalStart (rank, EV_ACTION_T_DYNAMITE, m_blupi[rank].cel);
        goto goal;
      }
    }
    cel = m_blupi[rank].cel;
    if (
      m_blupi[rank].goalAction == 0 &&
      m_time % 17 == rank % 17 && // pas trop souvent !
      SearchBombeObject (rank, cel, 100, cel, icon))
    {
      m_blupi[rank].goalCel = cel;
      FlushUsed (rank);
    }
  }

  // Assigne un but s'il s'agit d'un électro.
  if (m_blupi[rank].perso == 7) // électro ?
  {
    cel = m_blupi[rank].cel;
    if (cel.x % 2 != 0 && cel.y % 2 != 0)
    {
      cel.x = (cel.x / 2) * 2;
      cel.y = (cel.y / 2) * 2;
      GetObject (cel, channel, icon);
      if (channel == CHOBJECT && icon == 93) // piège ?
      {
        BlupiSound (rank, SOUND_TRAP, pos);
        PutObject (cel, CHOBJECT, 19); // électro piégée
        BlupiDelete (rank);            // supprime électro
        return;
      }
    }
    cel = m_blupi[rank].cel;
    if (
      m_blupi[rank].goalAction == 0 && m_blupi[rank].goalCel.x == -1 &&
      m_time % 37 == rank % 37 && // pas trop souvent !
      SearchElectroObject (rank, cel, 100, cel, icon))
    {
      if (icon == -1) // sur un blupi ?
      {
        BlupiInitAction (rank, ACTION_STOP);
        GoalStart (rank, EV_ACTION_E_RAYON, cel);
        m_blupi[rank].fix = cel;
        goto goal;
      }
      m_blupi[rank].goalCel = cel;
      FlushUsed (rank);
    }
  }

  BlupiInitAction (rank, ACTION_STOP);

goal:
  if (m_blupi[rank].goalCel.x != -1) // y a-t-il un but ?
  {
    direct = DirectSearch (m_blupi[rank].cel, m_blupi[rank].goalCel);

    if (direct == -1) // but atteint ?
    {
      m_blupi[rank].goalCel.x = -1;
      FlushUsed (rank);
    }
    else
    {
      // Si blupi a peu d'énergie et qu'il transporte
      // qq chose, il doit stopper !
      if (
        m_blupi[rank].energy <= MAXENERGY / 4 &&
        m_blupi[rank].takeChannel != -1 &&
        (m_blupi[rank].vehicule == 0 || // à pied ?
         m_blupi[rank].vehicule == 3))  // armure ?
      {
        // Si blupi est en train de descendre de la jeep
        // et qu'il est malade tout en transportant qq
        // chose, il ne faut pas stopper !!!
        cel = m_blupi[rank].cel;
        if (
          cel.x % 2 != 0 && cel.y % 2 != 0 &&
          m_decor[cel.x / 2][cel.y / 2].objectIcon == 118 && // jeep
          m_blupi[rank].bMalade && m_blupi[rank].takeChannel != -1)
          goto search;
        GoalStop (rank, true);
      }
      else
      {
      search:
        //-             BlupiInitAction(rank, ACTION_MARCHE, direct);
        if (SearchBestPass (rank, action))
        {
          //-                 if ( (action >= ACTION_SAUTE2 &&
          //-                       action <= ACTION_SAUTE5) ||
          //-                      action == ACTION_GLISSE )
          //-                 {
          BlupiInitAction (rank, action);
          //-                 }
        }
        else
        {
          BlupiInitAction (rank, ACTION_STOP);
          if (
            m_blupi[rank].perso == 0 || // blupi ?
            m_blupi[rank].perso == 8)   // disciple ?
          {
            if (m_blupi[rank].busyCount == 0) // dernière tentative ?
            {
              GoalStop (rank, true);
              m_blupi[rank].goalCel.x = -1;
              m_blupi[rank].goalPhase = 0;
              m_blupi[rank].interrupt = 1;
            }
          }
          else // perso ennemi ?
          {
            // On cherchera un autre but !
            GoalStop (rank, true);
            //?                     m_blupi[rank].goalCel.x = -1;
            //?                     m_blupi[rank].goalPhase = 0;
            //?                     m_blupi[rank].interrupt = 1;
          }
        }
      }
    }
  }
  else
  {
    GoalNextPhase (rank); // méta opération suivante
  }

init:
  BlupiDestCel (rank);

  m_blupi[rank].phase = 0;
  m_blupi[rank].pos.x = 0;
  m_blupi[rank].pos.y = 0;

  BlupiNextAction (rank);
}

// Calcule la cellule de destination.

void
CDecor::BlupiDestCel (Sint32 rank)
{
  Sint32 a;
  Point  vector;

  m_blupi[rank].destCel = m_blupi[rank].cel;

  if (
    m_blupi[rank].action == ACTION_WALK ||
    m_blupi[rank].action == ACTION_WALKTIRED ||
    m_blupi[rank].action == ACTION_MARCHEb ||
    m_blupi[rank].action == ACTION_WALKJEEP ||
    m_blupi[rank].action == ACTION_WALKARMOR ||
    m_blupi[rank].action == ACTION_S_WALK ||
    m_blupi[rank].action == ACTION_V_WALK ||
    m_blupi[rank].action == ACTION_T_WALK ||
    m_blupi[rank].action == ACTION_R_WALK ||
    m_blupi[rank].action == ACTION_B_WALK ||
    m_blupi[rank].action == ACTION_E_WALK ||
    m_blupi[rank].action == ACTION_D_WALK)
  {
    vector = GetVector (m_blupi[rank].sDirect);

    m_blupi[rank].destCel.x += vector.x;
    m_blupi[rank].destCel.y += vector.y;
  }

  a = GetAmplitude (m_blupi[rank].action);
  if (a > 1)
  {
    vector = GetVector (m_blupi[rank].sDirect);

    m_blupi[rank].destCel.x += vector.x * a;
    m_blupi[rank].destCel.y += vector.y * a;
  }
}

// Avance tous les blupis.

void
CDecor::BlupiStep (bool bFirst)
{
  Sint32 rank;

  for (rank = 0; rank < MAXBLUPI; rank++)
  {
    if (m_blupi[rank].bExist)
    {
      if (!BlupiNextAction (rank))
        BlupiNextGoal (rank);
    }
  }

  if (bFirst)
  {
    m_timeConst++; // avance le temps absolu global constant

    if (m_timeConst == m_timeFlipOutline)
    {
      m_bOutline = false; // supprime le mode "outline"
    }
  }
  m_time++; // avance le temps absolu global
}

// Retourne le rectangle occupé par un blupi,
// pour les sélections (pas exact).

void
CDecor::BlupiGetRect (Sint32 rank, Rect & rect)
{
  Point pos;

  pos = ConvCelToPos (m_blupi[rank].cel);
  pos.x += m_blupi[rank].pos.x;
  pos.y += m_blupi[rank].pos.y - (DIMBLUPIY - DIMCELY) - SHIFTBLUPIY;

  rect.left   = pos.x + 16;
  rect.top    = pos.y + 10;
  rect.right  = pos.x + DIMBLUPIX - 16;
  rect.bottom = pos.y + DIMBLUPIY;
}

// Retourne le blupi visé par la souris.

Sint32
CDecor::GetTargetBlupi (Point pos)
{
  Sint32 rank, found, prof;
  Point  test, rel, cel;

  cel = ConvPosToCel (pos);

  found = -1;
  prof  = 0;
  for (rank = 0; rank < MAXBLUPI; rank++)
  {
    if (
      m_blupi[rank].bExist && (m_blupi[rank].perso == 0 || // blupi ?
                               m_blupi[rank].perso == 8))  // disciple ?
    {
      test = ConvCelToPos (m_blupi[rank].cel);
      test.x += m_blupi[rank].pos.x;
      test.y += m_blupi[rank].pos.y - (DIMBLUPIY - DIMCELY) - SHIFTBLUPIY;

      if (
        pos.x >= test.x && pos.x <= test.x + DIMBLUPIX && pos.y >= test.y &&
        pos.y <= test.y + DIMBLUPIY)
      {
        rel.x = pos.x - test.x;
        rel.y = pos.y - test.y;
        if (
          (cel.x == m_blupi[rank].cel.x && cel.y == m_blupi[rank].cel.y) ||
          (cel.x == m_blupi[rank].destCel.x &&
           cel.y == m_blupi[rank].destCel.y) ||
          m_pPixmap->IsIconPixel (
            m_blupi[rank].channel, m_blupi[rank].icon, rel))
        {
          if (found != -1 && test.y < prof)
            continue;

          found = rank;
          prof  = test.y;
        }
      }
    }
  }

  return found;
}

// Déslectionne tous les blupi.

void
CDecor::BlupiDeselect ()
{
  Sint32 rank;

  for (rank = 0; rank < MAXBLUPI; rank++)
  {
    m_blupi[rank].bHili  = false;
    m_blupi[rank].bArrow = false;
  }

  m_nbBlupiHili   = 0;
  m_rankBlupiHili = -1;
}

// Déslectionne un blupi.

void
CDecor::BlupiDeselect (Sint32 rank)
{
  m_blupi[rank].bHili  = false;
  m_blupi[rank].bArrow = false;

  if (m_nbBlupiHili > 0 && m_rankBlupiHili == rank) // est-ce le blupi
                                                    // sélectionné ?
  {
    m_nbBlupiHili   = 0;
    m_rankBlupiHili = -1;
  }
}

// Met ou enlève une flèche au blupi sélectionné blupi.

void
CDecor::BlupiSetArrow (Sint32 rank, bool bArrow)
{
  m_celArrow.x = -1;

  if (bArrow)
    m_blupi[rank].bArrow = true;
  else
  {
    for (rank = 0; rank < MAXBLUPI; rank++)
      m_blupi[rank].bArrow = false;
  }
}

// Initialise la zone outline en fonction du rectangle de sélection.

void
CDecor::InitOutlineRect ()
{
  if (!m_bOutline && m_bHiliRect)
  {
    m_celOutline1.x = m_p1Hili.x < m_p2Hili.x ? m_p1Hili.x : m_p2Hili.x;
    m_celOutline1.y = m_p1Hili.y < m_p2Hili.y ? m_p1Hili.y : m_p2Hili.y;
    m_celOutline2.x = m_p1Hili.x > m_p2Hili.x ? m_p1Hili.x : m_p2Hili.x;
    m_celOutline2.y = m_p1Hili.y > m_p2Hili.y ? m_p1Hili.y : m_p2Hili.y;
    m_celOutline1.x = (m_celOutline1.x / 2) * 2;
    m_celOutline1.y = (m_celOutline1.y / 2) * 2;
    m_celOutline2.x += 2;
    m_celOutline2.y += 2;
  }
  else
  {
    m_celOutline1.x = -1;
    m_celOutline2.x = -1;
  }
}

// Sélectionne un blupi lorsque le bouton est pressé.

void
CDecor::BlupiHiliDown (Point pos, bool bAdd)
{
  if (MapMove (pos))
    return;

  if (!bAdd)
    BlupiDeselect ();

  m_p1Hili    = ConvPosToCel (pos);
  m_p2Hili    = ConvPosToCel (pos);
  m_bHiliRect = true;
  m_celHili.x = -1;

  InitOutlineRect ();
}

// Sélectionne un blupi lorsque la souris est déplacée.

void
CDecor::BlupiHiliMove (Point pos)
{
  if (m_bHiliRect) // rectangle de sélection existe ?
  {
    m_p2Hili = ConvPosToCel (pos);
    InitOutlineRect ();
  }
}

// Sélectionne un blupi lorsque le bouton est relâché.
// Retourne false si la sélection n'a pas changé !

void
CDecor::BlupiHiliUp (Point pos)
{
  Sint32 rank, r, nb;
  Sounds sound;
  bool   bEnerve = false;
  Point  c1, c2;

  static Sounds table_sound_ok[] = {
    SOUND_OK1, SOUND_OK2, SOUND_OK3, SOUND_OK4, SOUND_OK5, SOUND_OK6,
  };

  static Sounds table_sound_okf[] = // si fatigué
    {
      SOUND_OK1f, SOUND_OK2f, SOUND_OK3f,
    };

  static Sounds table_sound_oke[] = // si énervé
    {
      SOUND_OK1e, SOUND_OK2e, SOUND_OK3e,
    };

  if (m_bHiliRect) // rectangle de sélection existe ?
  {
    nb = 0;

    if (m_p1Hili.x == m_p2Hili.x && m_p1Hili.y == m_p2Hili.y)
    {
      rank = GetTargetBlupi (pos); // rank <- blupi visé par la souris
      if (rank != -1)
      {
        m_blupi[rank].bHili = !m_blupi[rank].bHili;
        if (m_blupi[rank].bHili)
        {
          if (m_blupi[rank].clicDelay > 0)
          {
            m_blupi[rank].clicDelay = 80;
            m_blupi[rank].clicCount++;
            if (m_blupi[rank].clicCount > 4)
              bEnerve = true;
          }
          else
            m_blupi[rank].clicDelay = 40;
          nb = 1;
        }
      }
    }
    else
    {
      if (m_p1Hili.x < m_p2Hili.x)
      {
        c1.x = m_p1Hili.x;
        c2.x = m_p2Hili.x + 1;
      }
      else
      {
        c1.x = m_p2Hili.x;
        c2.x = m_p1Hili.x + 1;
      }

      if (m_p1Hili.y < m_p2Hili.y)
      {
        c1.y = m_p1Hili.y;
        c2.y = m_p2Hili.y + 1;
      }
      else
      {
        c1.y = m_p2Hili.y;
        c2.y = m_p1Hili.y + 1;
      }

      for (r = 0; r < MAXBLUPI; r++)
      {
        if (
          m_blupi[r].bExist && (m_blupi[r].perso == 0 || // blupi ?
                                m_blupi[r].perso == 8))  // disciple ?
        {
          if (
            m_blupi[r].cel.x >= c1.x && m_blupi[r].cel.x < c2.x &&
            m_blupi[r].cel.y >= c1.y && m_blupi[r].cel.y < c2.y)
          {
            m_blupi[r].bHili = true;
            nb++;
            rank = r;
          }
        }
      }
    }

    m_bHiliRect = false; // plus de rectangle
    InitOutlineRect ();

    if (nb > 0)
    {
      if (nb > 1) // sélection multiple ?
        sound = table_sound_ok[Random (0, countof (table_sound_ok) - 1)];
      else
      {
        if (m_blupi[rank].energy <= MAXENERGY / 4)
          sound = table_sound_okf[Random (0, countof (table_sound_okf) - 1)];
        else
          sound = table_sound_ok[Random (0, countof (table_sound_ok) - 1)];
        if (bEnerve) // déjà sélectionné y'a peu ?
          sound = table_sound_oke[Random (0, countof (table_sound_oke) - 1)];
      }
      BlupiSound (rank, sound, pos, true);
    }
  }

  m_nbBlupiHili   = 0;
  m_rankBlupiHili = -1;
  for (rank = 0; rank < MAXBLUPI; rank++)
  {
    m_blupi[rank].bArrow = false;

    if (m_blupi[rank].bExist && m_blupi[rank].bHili)
    {
      m_nbBlupiHili++;
      m_rankBlupiHili = rank;
    }
  }
}

// Dessine le rectangle de sélection, si nécessaire.

void
CDecor::BlupiDrawHili ()
{
  Point  c1, c2, cc;
  Point  p1, p2, p3, p4;
  Point  start, pos;
  Rect   rect;
  Sint32 shift;

  if (!m_bHiliRect)
    return;

  if (m_p1Hili.x < m_p2Hili.x)
  {
    c1.x = m_p1Hili.x;
    c2.x = m_p2Hili.x + 1;
  }
  else
  {
    c1.x = m_p2Hili.x;
    c2.x = m_p1Hili.x + 1;
  }

  if (m_p1Hili.y < m_p2Hili.y)
  {
    c1.y = m_p1Hili.y;
    c2.y = m_p2Hili.y + 1;
  }
  else
  {
    c1.y = m_p2Hili.y;
    c2.y = m_p1Hili.y + 1;
  }

  p1 = ConvCelToPos (c1); // p1 en haut
  p2 = ConvCelToPos (c2); // p2 en bas

  cc.x = c1.x;
  cc.y = c2.y;
  p3   = ConvCelToPos (cc); // p3 à gauche

  cc.x = c2.x;
  cc.y = c1.y;
  p4   = ConvCelToPos (cc); // p4 à droite

  p1.x += DIMCELX / 2;
  p2.x += DIMCELX / 2;
  p3.x += DIMCELX / 2;
  p4.x += DIMCELX / 2;

  shift = m_shiftHili % (64 / 2);

  start.x = p1.x - shift * 2;
  start.y = p1.y - shift - 1;
  while (start.x < p4.x)
  {
    pos         = start;
    rect.left   = 0;
    rect.right  = 64;
    rect.top    = 0;
    rect.bottom = 66 / 2;
    if (pos.x + rect.right > p4.x)
      rect.right = p4.x - pos.x;
    if (pos.x < p1.x)
    {
      rect.left += p1.x - pos.x;
      rect.top += (p1.x - pos.x) / 2;
      pos.x = p1.x;
      pos.y = p1.y - 1;
    }
    m_pPixmap->DrawPart (-1, CHHILI, pos, rect); // ligne p1-p4

    start.x += 64;
    start.y += 64 / 2;
  }

  start.x = p3.x - shift * 2;
  start.y = p3.y - shift - 1;
  while (start.x < p2.x)
  {
    pos         = start;
    rect.left   = 0;
    rect.right  = 64;
    rect.top    = 0;
    rect.bottom = 66 / 2;
    if (pos.x + rect.right > p2.x)
      rect.right = p2.x - pos.x;
    if (pos.x < p3.x)
    {
      rect.left += p3.x - pos.x;
      rect.top += (p3.x - pos.x) / 2;
      pos.x = p3.x;
      pos.y = p3.y - 1;
    }
    m_pPixmap->DrawPart (-1, CHHILI, pos, rect); // ligne p3-p2

    start.x += 64;
    start.y += 64 / 2;
  }

  start.x = p3.x - shift * 2;
  start.y = p3.y + shift - 66 / 2;
  while (start.x < p1.x)
  {
    pos         = start;
    rect.left   = 0;
    rect.right  = 64;
    rect.top    = 66 / 2;
    rect.bottom = 66;
    if (pos.x + rect.right > p1.x)
      rect.right = p1.x - pos.x;
    if (pos.x < p3.x)
    {
      rect.left += p3.x - pos.x;
      rect.bottom -= (p3.x - pos.x) / 2;
      pos.x = p3.x;
    }
    m_pPixmap->DrawPart (-1, CHHILI, pos, rect); // ligne p3-p1

    start.x += 64;
    start.y -= 64 / 2;
  }

  start.x = p2.x - shift * 2;
  start.y = p2.y + shift - 66 / 2;
  while (start.x < p4.x)
  {
    pos         = start;
    rect.left   = 0;
    rect.right  = 64;
    rect.top    = 66 / 2;
    rect.bottom = 66;
    if (pos.x + rect.right > p4.x)
      rect.right = p4.x - pos.x;
    if (pos.x < p2.x)
    {
      rect.left += p2.x - pos.x;
      rect.bottom -= (p2.x - pos.x) / 2;
      pos.x = p2.x;
    }
    m_pPixmap->DrawPart (-1, CHHILI, pos, rect); // ligne p2-p4

    start.x += 64;
    start.y -= 64 / 2;
  }

  m_shiftHili += 3;
}

// Retourne le bouton par défaut à un endroit donné.
// Est utilisé pour trouver que faire lors d'un clic
// avec le bouton de droite.

Buttons
CDecor::GetDefButton (Point cel)
{
  Buttons button;
  Sint32  rank, channel, icon;
  Point   iCel;

  iCel  = cel;
  cel.x = (cel.x / 2) * 2;
  cel.y = (cel.y / 2) * 2;
  GetObject (cel, channel, icon);

  if (m_nbBlupiHili == 0)
    return BUTTON_NONE;
  if (m_nbBlupiHili > 1)
    return BUTTON_GO;

  rank = m_rankBlupiHili;

  button = BUTTON_GO;

  if (channel == CHOBJECT)
  {
    if (icon >= 6 && icon <= 11)
      button = BUTTON_ABAT;
    if (icon >= 37 && icon <= 43)
      button = BUTTON_ROC;

    if (icon == 61)
      button = BUTTON_CULTIVE; // cabane
    if (icon == 122)
      button = BUTTON_EXTRAIT; // extrait

    if (iCel.x % 2 == 1 && iCel.y % 2 == 1)
    {
      if (icon == 14)
        button = BUTTON_CARRY; // métal
      if (icon == 36)
        button = BUTTON_CARRY; // planches
      if (icon == 44)
        button = BUTTON_CARRY; // pierres
      if (icon == 60)
        button = BUTTON_EAT; // tomates
      if (icon == 63)
        button = BUTTON_CARRY; // oeufs
      if (icon == 80)
        button = BUTTON_BOIT; // bouteille
      if (icon == 82)
        button = BUTTON_CARRY; // fleurs
      if (icon == 84)
        button = BUTTON_CARRY; // fleurs
      if (icon == 95)
        button = BUTTON_CARRY; // fleurs
      if (icon == 85)
        button = BUTTON_CARRY; // dynamite
      if (icon == 92)
        button = BUTTON_CARRY; // poison
      if (icon == 93)
        button = BUTTON_CARRY; // piège
      if (icon == 123)
        button = BUTTON_CARRY; // fer
      if (icon == 125)
        button = BUTTON_CARRY; // mine
    }

    if (
      icon == 28 && // laboratoire ?
      m_blupi[rank].energy > MAXENERGY / 4 &&
      m_blupi[rank].takeChannel == CHOBJECT &&
      (m_blupi[rank].takeIcon == 82 || // porte fleurs ?
       m_blupi[rank].takeIcon == 84 || m_blupi[rank].takeIcon == 95 ||
       m_blupi[rank].takeIcon == 60)) // porte tomates ?
    {
      button = BUTTON_LABO; // transforme
    }
  }

  if (!m_blupi[rank].bMalade && button == BUTTON_BOIT)
  {
    button = BUTTON_CARRY; // porte la bouteille si pas malade
  }

  if (
    (m_blupi[rank].energy <= MAXENERGY / 4 ||
     m_blupi[rank].takeChannel != -1) &&
    (button == BUTTON_ABAT || button == BUTTON_CARRY || button == BUTTON_ROC ||
     button == BUTTON_CULTIVE))
    return BUTTON_NONE;

  if (m_blupi[rank].energy > (MAXENERGY / 4) * 3 && button == BUTTON_EAT)
    button = BUTTON_CARRY;

  if (m_buttonExist[button] == 0) // bouton existe ?
    return BUTTON_NONE;

  return button;
}

// Indique un but visé à Sint32 terme, pour un blupi donné.

bool
CDecor::BlupiGoal (Sint32 rank, Buttons button, Point cel, Point cMem)
{
  Point  goalHili, goalHili2, goal, test;
  Sint32 i, action, channel, icon, error, direct, step;
  bool   bRepeat = false;

  // Si plusieurs blupi sont sélectionnés, ils ne vont pas
  // tous à la même destination.
  if (button == BUTTON_GO)
  {
    step = 0;
    for (i = 0; i < rank; i++)
    {
      if (m_blupi[i].bExist && m_blupi[i].bHili)
        step++;
    }
    if (step > 15)
      step = 15;
    cel.x += table_multi_goal[step * 2 + 0];
    cel.y += table_multi_goal[step * 2 + 1];
    cMem.x += table_multi_goal[step * 2 + 0];
    cMem.y += table_multi_goal[step * 2 + 1];
  }

  if (!IsCheminFree (rank, cel, button))
    return false;

  goal        = cel;
  goalHili    = cel;
  goalHili2.x = (cel.x / 2) * 2;
  goalHili2.y = (cel.y / 2) * 2;

  if (
    button == BUTTON_GO &&
    m_decor[goalHili.x / 2][goalHili.y / 2].objectIcon == 113) // maison ?
  {
    goalHili.x = (goalHili.x / 2) * 2 + 1;
    goalHili.y = (goalHili.y / 2) * 2 + 1;
  }

  if (button == BUTTON_ABATn)
  {
    button  = BUTTON_ABAT;
    bRepeat = true;
  }
  if (button == BUTTON_ROCn)
  {
    button  = BUTTON_ROC;
    bRepeat = true;
  }
  if (button == BUTTON_FLOWERn)
  {
    button  = BUTTON_FLOWER;
    bRepeat = true;
  }
  action = table_actions[button];

  if (action == EV_ACTION_STOP)
  {
    if (m_blupi[rank].goalAction != 0 && m_blupi[rank].interrupt <= 0)
    {
      m_blupi[rank].stop = 1; // faudra stopper
    }
    else
    {
      m_blupi[rank].goalCel    = m_blupi[rank].destCel;
      m_blupi[rank].goalAction = 0;
    }
    m_blupi[rank].repeatLevel = -1; // stoppe la répétition
    return false;
  }

  // Action prioritaire en cours ?
  if (m_blupi[rank].goalAction != 0 && m_blupi[rank].interrupt <= 0)
    return false;

  error = CelOkForAction (goalHili, action, rank);
  if (error != 0 && error != Errors::TOURISOL)
    return false;

  if (
    action == EV_ACTION_GO && m_blupi[rank].energy <= MAXENERGY / 4 &&
    m_blupi[rank].takeChannel != -1)
    return false;

  if (action == EV_ACTION_GO)
  {
    GetObject (goalHili2, channel, icon);
    if (
      channel == CHOBJECT && icon == 120 && // usine ?
      goalHili.x % 2 == 0 &&                // au fond ?
      goalHili.y % 2 == 1)
    {
      return false; // action refusée
    }
    if (
      m_blupi[rank].perso != 8 &&           // pas disciple ?
      channel == CHOBJECT && icon == 118 && // jeep ?
      goalHili.x % 2 == 1 &&                // sur la jeep ?
      goalHili.y % 2 == 1)
      action = EV_ACTION_MJEEP;
    if (
      m_blupi[rank].perso != 8 &&          // pas disciple ?
      m_blupi[rank].takeChannel == -1 &&   // ne porte rien ?
      channel == CHOBJECT && icon == 16 && // armure ?
      goalHili.x % 2 == 1 &&               // sur l'armure ?
      goalHili.y % 2 == 1)
      action = EV_ACTION_MARMURE;
    if (
      m_blupi[rank].perso != 8 &&         // pas disciple ?
      channel == CHOBJECT && icon == 113) // maison ?
      action = EV_ACTION_HOUSE;
    GetFloor (goalHili2, channel, icon);
    if (
      m_blupi[rank].perso == 0 && m_blupi[rank].vehicule == 0 && // à pied ?
      m_blupi[rank].takeChannel == -1 && // ne porte rien ?
      channel == CHFLOOR && icon == 80)  // téléporteur ?
    {
      if (cel.x % 2 == 0 && cel.y % 2 == 0)
        action = EV_ACTION_TELEPORTE00;
      if (cel.x % 2 == 1 && cel.y % 2 == 0)
        action = EV_ACTION_TELEPORTE10;
      if (cel.x % 2 == 0 && cel.y % 2 == 1)
        action = EV_ACTION_TELEPORTE01;
      if (cel.x % 2 == 1 && cel.y % 2 == 1)
        action = EV_ACTION_TELEPORTE11;
    }
    IsFreeCelEmbarque (goalHili, rank, action, goal);
    IsFreeCelDebarque (goalHili, rank, action, goal);
  }

  if (action == EV_ACTION_DROP && m_blupi[rank].energy <= MAXENERGY / 4)
  {
    // Energie juste pour déposer l'objet transporté.
    m_blupi[rank].energy = MAXENERGY / 4 + 20;
  }

  if (action == EV_ACTION_ABAT1)
  {
    GetObject (goalHili2, channel, icon);
    if (channel == CHOBJECT && icon >= 6 && icon <= 11) // arbre ?
    {
      action += icon - 6; // EV_ACTION_ABAT1..6
    }
  }

  if (action == EV_ACTION_ROC1)
  {
    GetObject (goalHili2, channel, icon);
    if (channel == CHOBJECT && icon >= 37 && icon <= 43) // rochers ?
    {
      action += icon - 37; // EV_ACTION_ROC1..7
    }
  }

  if (action == EV_ACTION_FLOWER1)
  {
    GetObject (goalHili2, channel, icon);
    if (channel == CHOBJECT && icon == 83) // fleurs foncées ?
      action = EV_ACTION_FLOWER2;
    if (channel == CHOBJECT && icon == 94) // fleurs vertes ?
      action = EV_ACTION_FLOWER3;
  }

  if (action == EV_ACTION_BRIDGEE)
  {
    cel  = goalHili2;
    test = goalHili2;
    if (IsBuildPont (test, icon) != 0)
      return false;

    m_blupi[rank].nLoop =
      static_cast<Sint16> (abs ((test.x - cel.x) + (test.y - cel.y)) / 2);
    m_blupi[rank].cLoop = 0;
    m_blupi[rank].vIcon = icon;
    m_blupi[rank].fix   = cel;

    if (test.x - cel.x < 0)
      action = EV_ACTION_BRIDGEO;
    if (test.y - cel.y > 0)
      action = EV_ACTION_BRIDGES;
    if (test.y - cel.y < 0)
      action = EV_ACTION_BRIDGEN;
  }

  if (action == EV_ACTION_BOATE)
  {
    if (!IsBuildBateau (goalHili2, direct))
      return false;

    if (direct == DIRECT_S)
      action = EV_ACTION_BOATS;
    if (direct == DIRECT_W)
      action = EV_ACTION_BOATO;
    if (direct == DIRECT_N)
      action = EV_ACTION_BOATN;
  }

  if (action == EV_ACTION_CARRY)
  {
    if (IsBlupiHereEx (GetCel (goalHili2, 0, 1), rank, true))
      action = EV_ACTION_CARRY2;
  }

  if (action == EV_ACTION_DROP)
  {
    GetFloor (goalHili2, channel, icon);
    if (
      channel == CHFLOOR && icon == 52 && // nurserie ?
      m_blupi[rank].takeChannel == CHOBJECT &&
      m_blupi[rank].takeIcon == 63) // oeufs ?
      action = EV_ACTION_NEWBLUPI;
    if (
      !IsFreeCelDepose (GetCel (goalHili2, 0, 1), rank) ||
      IsBlupiHereEx (GetCel (goalHili2, 0, 1), rank, true))
      action = EV_ACTION_DROP2;
  }

  if (action == EV_ACTION_EAT)
  {
    if (IsBlupiHereEx (GetCel (goalHili2, 0, 1), rank, true))
      action = EV_ACTION_EAT2;
  }

  if (action == EV_ACTION_DRINK)
  {
    if (IsBlupiHereEx (GetCel (goalHili2, 0, 1), rank, true))
      action = EV_ACTION_DRINK2;
  }

  if (action == EV_ACTION_DYNAMITE)
  {
    GetObject (goalHili2, channel, icon);
    if (channel == CHOBJECT && icon == 125) // mine ?
      action = EV_ACTION_MINE;
    else
    {
      if (
        m_blupi[rank].takeChannel == CHOBJECT &&
        m_blupi[rank].takeIcon == 85) // porte dynamite ?
        action = EV_ACTION_DYNAMITE2;
    }
  }

  GoalStart (rank, action, goal);
  m_blupi[rank].bRepeat   = bRepeat;
  m_blupi[rank].busyCount = 5; // 5 tentatives au maximum
  m_blupi[rank].busyDelay = 0;

  if (action == EV_ACTION_REPEAT)
  {
    m_blupi[rank].repeatLevel = m_blupi[rank].repeatLevelHope;
    m_blupi[rank].listCel[m_blupi[rank].repeatLevel] = goal;
  }
  else
    ListPut (rank, button, goal, cMem);

  return true;
}

// Indique un but visé à Sint32 terme, pour tous les blupi
// sélectionnés.

void
CDecor::BlupiGoal (Point cel, Buttons button)
{
  Point  bPos, avg;
  Sint32 rank, nb, nbHili;

  static Sounds table_sound_go[] = {
    SOUND_GO1, SOUND_GO2, SOUND_GO3, SOUND_GO4, SOUND_GO5, SOUND_GO6,
  };

  static Sounds table_sound_gom[] = {
    SOUND_GO4, SOUND_GO5, SOUND_GO6,
  };

  static Sounds table_sound_boing[] = {
    SOUND_BOING1, SOUND_BOING2, SOUND_BOING3,
  };

  if (button == -1)
  {
    avg = ConvCelToPos (cel);
    m_pSound->PlayImage (
      table_sound_boing[Random (0, countof (table_sound_boing) - 1)], avg);
    return;
  }

  avg.x  = 0;
  avg.y  = 0;
  nb     = 0;
  nbHili = 0;
  for (rank = 0; rank < MAXBLUPI; rank++)
  {
    if (m_blupi[rank].bExist && m_blupi[rank].bHili)
    {
      bPos = ConvCelToPos (m_blupi[rank].cel);
      avg.x += bPos.x;
      avg.y += bPos.y;
      nbHili++;

      if (BlupiGoal (rank, button, cel, cel))
        nb++;
    }
  }

  if (button == BUTTON_STOP)
    return;

  if (nbHili > 0)
  {
    avg.x /= nbHili;
    avg.y /= nbHili;
  }
  if (avg.x < 0)
    avg.x = 0;
  if (avg.x > LXIMAGE ())
    avg.x = LXIMAGE ();
  avg.y = LYIMAGE () / 2;

  if (nb == 0 && nbHili > 0)
  {
    if (nbHili == 1)
      BlupiSound (m_rankBlupiHili, table_sound_boing[Random (0, 2)], avg, true);
    else
      m_pSound->PlayImage (table_sound_boing[Random (0, 2)], avg);
  }

  if (nb > 0)
  {
    if (nbHili == 1)
      BlupiSound (m_rankBlupiHili, table_sound_go[Random (0, 5)], avg, true);
    else
      m_pSound->PlayImage (table_sound_gom[Random (0, 2)], avg);
  }
}

// Indique si une cellule est occupée pour un tracks.
// La cellule est considérée libre uniquement si elle
// contient un blupi à pied ou un détonnateur de mine
// (personnage invisible).

bool
CDecor::IsTracksHere (Point cel, bool bSkipInMove)
{
  Sint32 rank;

  if (!IsValid (cel))
    return false;

  for (rank = 0; rank < MAXBLUPI; rank++)
  {
    if (
      m_blupi[rank].bExist &&
      (m_blupi[rank].perso != 0 ||    // blupi ?
       m_blupi[rank].vehicule != 0 || // à pied ?
       m_bInvincible) &&
      m_blupi[rank].perso != 6) // détonnateur ?
    {
      if (bSkipInMove && m_blupi[rank].goalCel.x != -1)
        continue;

      if (cel.x == m_blupi[rank].cel.x && cel.y == m_blupi[rank].cel.y)
      {
        m_blupiHere = rank;
        return true;
      }

      if (cel.x == m_blupi[rank].destCel.x && cel.y == m_blupi[rank].destCel.y)
      {
        m_blupiHere = rank;
        return true;
      }
    }
  }

  return false;
}

// Indique si une cellule est occupée par un blupi.
// Le blupi donné dans exRank est ignoré !

bool
CDecor::IsBlupiHereEx (Point cel1, Point cel2, Sint32 exRank, bool bSkipInMove)
{
  Sint32 rank;

  if (!IsValid (cel1))
    return false;
  if (!IsValid (cel2))
    return false;

  for (rank = 0; rank < MAXBLUPI; rank++)
  {
    if (
      m_blupi[rank].bExist &&
      m_blupi[rank].perso != 6 && // pas le détonnateur de mine
      rank != exRank)
    {
      if (bSkipInMove && m_blupi[rank].goalCel.x != -1)
        continue;

      if (
        cel1.x <= m_blupi[rank].cel.x && cel2.x >= m_blupi[rank].cel.x &&
        cel1.y <= m_blupi[rank].cel.y && cel2.y >= m_blupi[rank].cel.y)
      {
        m_blupiHere = rank;
        return true;
      }

      if (
        cel1.x <= m_blupi[rank].destCel.x &&
        cel2.x >= m_blupi[rank].destCel.x &&
        cel1.y <= m_blupi[rank].destCel.y && cel2.y >= m_blupi[rank].destCel.y)
      {
        m_blupiHere = rank;
        return true;
      }
    }
  }

  return false;
}

// Indique si une cellule est occupée par un blupi.
// Le blupi donné dans exRank est ignoré !

bool
CDecor::IsBlupiHereEx (Point cel, Sint32 exRank, bool bSkipInMove)
{
  Sint32 rank;

  if (!IsValid (cel))
    return false;

  for (rank = 0; rank < MAXBLUPI; rank++)
  {
    if (
      m_blupi[rank].bExist &&
      m_blupi[rank].perso != 6 && // pas le détonnateur de mine
      rank != exRank)
    {
      if (bSkipInMove && m_blupi[rank].goalCel.x != -1)
        continue;

      if (cel.x == m_blupi[rank].cel.x && cel.y == m_blupi[rank].cel.y)
      {
        m_blupiHere = rank;
        return true;
      }

      if (cel.x == m_blupi[rank].destCel.x && cel.y == m_blupi[rank].destCel.y)
      {
        m_blupiHere = rank;
        return true;
      }
    }
  }

  return false;
}

// Indique si une cellule est occupée par un blupi.

bool
CDecor::IsBlupiHere (Point cel, bool bSkipInMove)
{
  return IsBlupiHereEx (cel, -1, bSkipInMove);
}

// Indique si une cellule future (dans une direction donnée)
// est déjà occupée par un blupi.

bool
CDecor::IsBlupiHere (Point cel, Sint32 direct, bool bSkipInMove)
{
  Point vector;

  vector = GetVector (direct);

  cel.x += vector.x;
  cel.y += vector.y;

  return IsBlupiHereEx (cel, -1, bSkipInMove);
}

// Retourne les niveaux des jauges.

void
CDecor::GetLevelJauge (Sint32 * pLevels, Sint32 * pTypes)
{
  Sint32 rank;

  pLevels[0] = -1;
  pLevels[1] = -1;

  rank = m_rankBlupiHili;

  if (m_nbBlupiHili == 1) // un seul blupi sélectionné ?
  {
    pLevels[0] = (m_blupi[rank].energy * 100) / MAXENERGY;

    pTypes[0] = 1; // rouge
    if (m_blupi[rank].energy > MAXENERGY / 4)
    {
      pTypes[0] = 2; // bleu
    }
  }

  if (m_blupi[rank].interrupt == 0 && m_blupi[rank].jaugeMax > 0)
  {
    pLevels[1] = (m_blupi[rank].jaugePhase * 100) / m_blupi[rank].jaugeMax;
    pTypes[1]  = 3; // jaune
  }
}

// Retourne true si un blupi est déjà sélectionné et qu'il
// effectue une action prioritaire. Dans ce cas, il faut tout
// de suite mettre le menu "stoppe" s'il est cliqué.

bool
CDecor::IsWorkBlupi (Sint32 rank)
{
  if (
    m_blupi[rank].bHili && m_blupi[m_rankBlupiHili].goalAction != 0 &&
    m_blupi[m_rankBlupiHili].interrupt <= 0)
    return true;

  return false;
}

// Retourne les boutons possibles à un endroit donné,
// pour le blupi sélectionné.

void
CDecor::BlupiGetButtons (
  Point pos, Sint32 & nb, Buttons * pButtons, Errors * pErrors,
  std::unordered_map<Sint32, const char *> & texts, Sint32 & perso)
{
  Buttons *    pB = pButtons;
  Errors *     pE = pErrors;
  Point        cel, cel2;
  Sint32       i, rank, channel, icon;
  Errors       error;
  Buttons      button;
  bool         bBuild = false;
  bool         bPut;
  const char * textForButton;

  static struct {
    Buttons button;
    Sint32  icon;
  } table_buttons[] = {
    //
    {BUTTON_GO, 0},        //
    {BUTTON_DJEEP, 0},     //
    {BUTTON_DARMOR, 0},    //
    {BUTTON_EAT, 0},       //
    {BUTTON_BOIT, 0},      //
    {BUTTON_CARRY, 0},     //
    {BUTTON_DEPOSE, 0},    //
    {BUTTON_LABO, 0},      //
    {BUTTON_ABAT, 0},      //
    {BUTTON_ABATn, 0},     //
    {BUTTON_ROC, 0},       //
    {BUTTON_ROCn, 0},      //
    {BUTTON_CULTIVE, 0},   //
    {BUTTON_FLOWER, 0},    //
    {BUTTON_FLOWERn, 0},   //
    {BUTTON_DYNAMITE, 0},  //
    {BUTTON_FLAG, 0},      //
    {BUTTON_EXTRAIT, 0},   //
    {BUTTON_FABJEEP, 0},   //
    {BUTTON_FABMINE, 0},   //
    {BUTTON_FABDISC, 0},   //
    {BUTTON_MAKEARMOR, 0}, //
    {BUTTON_BUILD1, 36},   // si planches (cabane)
    {BUTTON_BUILD2, 36},   // si planches (nurserie)
    {BUTTON_BUILD4, 36},   // si planches (mine)
    {BUTTON_PALIS, 36},    // si planches
    {BUTTON_BRIDGE, 36},   // si planches
    {BUTTON_BOAT, 36},     // si planches
    {BUTTON_BUILD6, 36},   // si planches (téléporteur)
    {BUTTON_BUILD3, 44},   // si pierres (laboratoire)
    {BUTTON_BUILD5, 44},   // si pierres (usine)
    {BUTTON_WALL, 44},     // si pierres
    {BUTTON_TOWER, 44},    // si pierres
    {BUTTON_STOP, 0},      //
    {BUTTON_NONE, 0}       //
  };

  nb    = 0;
  perso = 0;

  cel  = ConvPosToCel (pos);
  cel2 = ConvPosToCel2 (pos);

  if (m_nbBlupiHili == 0)
    return;

  if (m_nbBlupiHili > 1) // sélection multiple ?
  {
    error = CelOkForAction (cel, table_actions[BUTTON_GO], m_rankBlupiHili);
    if (error == 0)
    {
      *pB++ = BUTTON_GO;
      *pE++ = Errors::NONE;
      nb++;
    }

    for (rank = 0; rank < MAXBLUPI; rank++)
    {
      if (m_blupi[rank].bExist && m_blupi[rank].goalAction != 0)
      {
        *pB++ = BUTTON_STOP;
        *pE++ = Errors::NONE;
        nb++;
        break;
      }
    }

    return;
  }

  if (m_nbBlupiHili != 1)
    return;

  perso = m_blupi[m_rankBlupiHili].perso;

  // Si action prioritaire en cours -> seulement stoppe.
  if (
    m_blupi[m_rankBlupiHili].goalAction != 0 &&
    m_blupi[m_rankBlupiHili].interrupt <= 0)
  {
    if (
      abs (m_blupi[m_rankBlupiHili].cel.x - cel.x) <= 3 &&
      abs (m_blupi[m_rankBlupiHili].cel.y - cel.y) <= 3 &&
      CelOkForAction (cel, table_actions[BUTTON_STOP], m_rankBlupiHili) == 0)
    {
      *pB++ = BUTTON_STOP;
      *pE++ = Errors::NONE;
      nb++;
    }
    return;
  }

  // Vérifie si le blupi sélectionné peut construire.
  if (m_rankBlupiHili >= 0)
  {
    if (
      m_blupi[m_rankBlupiHili].energy > MAXENERGY / 4 &&
      m_blupi[m_rankBlupiHili].takeChannel == -1 &&
      m_blupi[m_rankBlupiHili].vehicule == 0) // à pied ?
      bBuild = true;
  }

  // Met les différentes actions.
  for (size_t i = 0; i < countof (table_buttons); ++i)
  {
    button = table_buttons[i].button;

    if (m_buttonExist[button] == 0)
      continue;

    error = CelOkForAction (cel, table_actions[button], m_rankBlupiHili);

    if (error == 0)
      bPut = true;
    else
      bPut = false;

    if (
      bBuild && table_buttons[i].icon != 0 && // toujours présent si matière ?
      (m_rankBlupiHili < 0 ||
       m_blupi[m_rankBlupiHili].perso != 8 || // pas disciple ?
       table_buttons[i].icon != 44))          // ni pierres ?
    {
      GetObject (cel2, channel, icon);
      if (
        channel == CHOBJECT && icon == table_buttons[i].icon && // matière ?
        cel.x % 2 == 1 && cel.y % 2 == 1)
      {
        bPut = true; // bouton présent, mais disable !
      }
    }

    if (bPut)
    {
      *pB++ = button;
      *pE++ = error;
      nb++;
    }
  }

  // Si le premier bouton est "abat", ajoute "va" devant !
  if (pButtons[0] == BUTTON_ABAT)
  {
    for (i = nb; i > 0; i--)
    {
      pButtons[i] = pButtons[i - 1];
      pErrors[i]  = pErrors[i - 1];
    }

    pButtons[0] = BUTTON_GO;
    pErrors[0]  = Errors::MISC;
    nb++;
  }

  // Regarde s'il faut ajouter le bouton "répète".
  if (
    m_blupi[m_rankBlupiHili].repeatLevel != -1 ||
    m_blupi[m_rankBlupiHili].energy <= MAXENERGY / 4 ||
    m_buttonExist[BUTTON_REPEAT] == 0)
    return;

  for (i = 0; i < nb; i++)
  {
    rank = ListSearch (m_rankBlupiHili, pButtons[i], cel, textForButton);
    if (rank > 0) // au moins 2 actions à répéter ?
    {
      m_blupi[m_rankBlupiHili].repeatLevelHope = rank;

      pButtons[nb] = BUTTON_REPEAT;
      pErrors[nb]  = Errors::REPEAT;
      texts[nb]    = textForButton;
      nb++;
      return;
    }
  }
}

// Initialise les conditions de fin.

void
CDecor::TerminatedInit ()
{
  m_winCount = 50;

  m_winLastHachBlupi   = 0;
  m_winLastHachPlanche = 0;
  m_winLastHachTomate  = 0;
  m_winLastHachMetal   = 0;
  m_winLastHachRobot   = 0;
  m_winLastHome        = 0;
  m_winLastHomeBlupi   = 0;
  m_winLastRobots      = 0;
}

// Vérifie si la partie est terminée.
// Retourne 0 si la partie n'est pas terminée.
// Retourne 1 si la partie est perdue.
// Retourne 2 si la partie est gagnée.

Sint32
CDecor::IsTerminated ()
{
  Sint32 nb, count, out;
  Point  pos;

  pos.x = LXIMAGE () / 2;
  pos.y = LYIMAGE () / 2;

  count      = m_winCount;
  m_winCount = 50;

  if (m_winLastHome > m_nbStatHome) // une maison en moins ?
  {
    out = 1; // perdu
    goto delay;
  }
  m_winLastHome = m_nbStatHome;

  nb = StatisticGetBlupi ();
  if (nb < m_term.nbMinBlupi)
  {
    out = 1; // perdu
    goto delay;
  }
  if (nb < m_term.nbMaxBlupi)
    return 0; // continue

  if (m_term.bStopFire)
  {
    nb = StatisticGetFire ();
    if (nb > 0)
      return 0; // continue;
  }

  if (m_term.bHachBlupi)
  {
    if (m_winLastHachBlupi < m_nbStatHachBlupi)
      m_pSound->PlayImage (SOUND_GOAL, pos);
    m_winLastHachBlupi = m_nbStatHachBlupi;

    if (m_nbStatHachBlupi < m_nbStatHach * m_term.nbMinBlupi)
      return 0; // continue;
  }

  if (m_term.bHachPlanche)
  {
    if (m_winLastHachPlanche < m_nbStatHachPlanche)
      m_pSound->PlayImage (SOUND_GOAL, pos);
    m_winLastHachPlanche = m_nbStatHachPlanche;

    if (m_nbStatHachPlanche < m_nbStatHach)
      return 0; // continue;
  }

  if (m_term.bHachTomate)
  {
    if (m_winLastHachTomate < m_nbStatHachTomate)
      m_pSound->PlayImage (SOUND_GOAL, pos);
    m_winLastHachTomate = m_nbStatHachTomate;

    if (m_nbStatHachTomate < m_nbStatHach)
      return 0; // continue;
  }

  if (m_term.bHachMetal)
  {
    if (m_winLastHachMetal < m_nbStatHachMetal)
      m_pSound->PlayImage (SOUND_GOAL, pos);
    m_winLastHachMetal = m_nbStatHachMetal;

    if (m_nbStatHachMetal < m_nbStatHach)
      return 0; // continue;
  }

  if (m_term.bHachRobot)
  {
    if (m_winLastRobots > m_nbStatRobots)
    {
      out = 1; // perdu
      goto delay;
    }
    m_winLastRobots = m_nbStatRobots;

    if (m_winLastHachRobot < m_nbStatHachRobot)
      m_pSound->PlayImage (SOUND_GOAL, pos);
    m_winLastHachRobot = m_nbStatHachRobot;

    if (m_nbStatHachRobot < m_nbStatHach)
      return 0; // continue;
  }

  if (m_term.bHomeBlupi)
  {
    if (m_winLastHomeBlupi < m_nbStatHomeBlupi)
      m_pSound->PlayImage (SOUND_GOAL, pos);
    m_winLastHomeBlupi = m_nbStatHomeBlupi;

    if (m_nbStatHomeBlupi < m_nbStatHome)
      return 0; // continue;
  }

  if (m_term.bKillRobots)
  {
    if (m_winLastRobots > m_nbStatRobots)
      m_pSound->PlayImage (SOUND_GOAL, pos);
    m_winLastRobots = m_nbStatRobots;

    if (m_nbStatRobots > 0)
      return 0; // continue;
  }

  out = 2; // gagné

delay:
  m_winCount = count;
  if (m_winCount == 0)
  {
    if (out == 1) // perdu ?
    {
      if (!m_pSound->PlayImage (SOUND_LOST, pos))
        m_pSound->PlayImage (SOUND_GOAL, pos);
    }
    else
    {
      if (!m_pSound->PlayImage (SOUND_WIN, pos))
        m_pSound->PlayImage (SOUND_GOAL, pos);
    }

    return out; // perdu/gagné
  }
  m_winCount--;
  return 0; // continue
}

// Retourne la structure pour terminer une partie.

Term *
CDecor::GetTerminated ()
{
  return &m_term;
}