Устройство системы чит-кодов в The Simpsons: Hit & Run

Демонстрация активированного чита CHEAT_ID_ONE_TAP_TRAFFIC_DEATH

Демонстрация активированного чита CHEAT_ID_ONE_TAP_TRAFFIC_DEATH

Приветствую всех мододелов и интересующихся!

Сегодня я хочу обсудить внутреннее устройство системы чит-кодов в The Simpsons: Hit & Run 2003.

Статья будет разбита на 4 части, в первых трех из которых будет обсуждаться устройство самой системы чит-кодов, а в заключительной четвертой части будет рассказано, как добавить свои чит-коды в игру.

Чит-код

Начнем пожалуй с того, что такое чит-код в The Simpsons: Hit & Run:

Чит-код — это комбинация из четырех назначенных заранее клавиш, которые при выполнении этой комбинации выполняют какое-то действие.

Так как комбинация состоит из четырех клавиш, то всего чит-кодов может быть не больше 256 (кол-во всевозможных комбинаций четырех клавиш равно 256). Полный список читов, используемых и неиспользуемых в игре, можно найти в файле cheats.h:

enum eCheatID
{
    CHEAT_ID_UNREGISTERED = -1,

    CHEAT_ID_MOTHER_OF_ALL_CHEATS,  // display all cheats on screen

    // this is not actually a cheat, but is merely to indicate that
    // all cheat id's following this will be enabled whenever the
    // CHEAT_ID_UNLOCK_EVERYTHING cheat is enabled
    //
    CHEAT_ID_UNLOCK_BEGIN,

    CHEAT_ID_UNLOCK_CARDS = CHEAT_ID_UNLOCK_BEGIN,
    CHEAT_ID_UNLOCK_SKINS,
    CHEAT_ID_UNLOCK_MISSIONS,
    CHEAT_ID_UNLOCK_MOVIES,
    CHEAT_ID_UNLOCK_VEHICLES,
    CHEAT_ID_UNLOCK_EVERYTHING,     // unlock everything!!!

    CHEAT_ID_NO_TOP_SPEED,
    CHEAT_ID_HIGH_ACCELERATION,
    CHEAT_ID_CAR_JUMP_ON_HORN,
    CHEAT_ID_FULL_DAMAGE_TO_CAR,
    CHEAT_ID_ONE_TAP_TRAFFIC_DEATH,
    CHEAT_ID_EXTRA_TIME,
    CHEAT_ID_SHOW_AVATAR_POSITION,
    CHEAT_ID_KICK_TOGGLES_CHARACTER_MODEL,
    CHEAT_ID_EXTRA_COINS,
    CHEAT_ID_UNLOCK_CAMERAS,
    CHEAT_ID_SPEED_CAM = CHEAT_ID_UNLOCK_CAMERAS,
    CHEAT_ID_DEMO_TEST,
    CHEAT_ID_PLAY_CREDITS_DIALOG,
    CHEAT_ID_SHOW_SPEEDOMETER,
    CHEAT_ID_REDBRICK,
    CHEAT_ID_INVINCIBLE_CAR,
    CHEAT_ID_SHOW_TREE,
    CHEAT_ID_TRIPPY,

    NUM_CHEATS
};

Часть 1 (CheatDB)

CheatDB — класс, хранящий массив типов зарегистрированных читов m_cheaths (Зарегистрированный чит — чит, к которому прикреплена некоторая комбинация клавиш (то есть это связка самого типа чита и некоторой комбинации клавиш)).

Скрытый текст

(*) Интерфейс CheatDB:

class CheatsDB
{
public:
	CheatsDB();
    virtual ~CheatsDB();

    eCheatID GetCheatID( unsigned int cheatIndex ) const;

    unsigned int GetNumRegisteredCheats() const;
    const Cheat* GetCheat( eCheatID cheatID ) const;

    static unsigned int ConvertSequenceToIndex( const eCheatInput* cheatInputs,
                                                int numInputs = NUM_CHEAT_SEQUENCE_INPUTS );

    static void PrintCheatInfo( const Cheat* cheat, char* buffer );

private:
    //---------------------------------------------------------------------
    // Private Functions
    //---------------------------------------------------------------------

    // No copying or assignment. Declare but don't define.
    //
    CheatsDB( const CheatsDB& );
    CheatsDB& operator= ( const CheatsDB& );

    //---------------------------------------------------------------------
    // Private Data
    //---------------------------------------------------------------------

    static unsigned int s_maxNumPossibleCheats;
    eCheatID* m_cheats;

};

unsigned int CheatsDB::s_maxNumPossibleCheats = 1;

Конструктор CheatDB

Разберем код конструктора CheatDB:

Скрытый текст

(*) Полный код конструктора CheatDB:

CheatsDB::CheatsDB()
:   m_cheats( NULL )
{
MEMTRACK_PUSH_GROUP( "CheatsDB" );
    unsigned int i = 0;

    for( i = 0; i < NUM_CHEAT_SEQUENCE_INPUTS; i++ )
    {
        s_maxNumPossibleCheats *= NUM_CHEAT_INPUTS;
    }

    m_cheats = new( GMA_PERSISTENT ) eCheatID[ s_maxNumPossibleCheats ];
    for( i = 0; i < s_maxNumPossibleCheats; i++ )
    {
        m_cheats[ i ] = CHEAT_ID_UNREGISTERED;
    }

    rTunePrintf( "\n---=[ Registered Simpsons Cheats Begin ]=---\n\n" );

    for( i = 0; i < NUM_REGISTERED_CHEATS; i++ )
    {
        int cheatIndex = this->ConvertSequenceToIndex( REGISTERED_CHEATS[ i ].m_cheatInputs );

        rAssertMsg( m_cheats[ cheatIndex ] == CHEAT_ID_UNREGISTERED,
                    "WARNING: *** Duplicate cheat input sequence found! Clobbering previously registered cheat." );

        rAssert( REGISTERED_CHEATS[ i ].m_cheatID < static_cast( MAX_NUM_CHEATS ) );
        m_cheats[ cheatIndex ] = REGISTERED_CHEATS[ i ].m_cheatID;

#ifndef RAD_RELEASE
        char buffer[ 256 ];
        this->PrintCheatInfo( &(REGISTERED_CHEATS[ i ]), buffer );

        rTunePrintf( "Cheat ID [%02d]: %s\n",
                     REGISTERED_CHEATS[ i ].m_cheatID, buffer );
#endif
    }

    rTunePrintf( "\n---=[ Registered Simpsons Cheats End   ]=---\n\n" );
MEMTRACK_POP_GROUP( "CheatsDB" );
}

В начале конструктора происходит инициализация переменной s_maxNumPossibleCheats (которая отображает максимально возможное кол-во зарегистрированных читов): так как зарегистрированный чит это комбинация типа чита и комбинации клавиш, то максимально возможное кол-во зарегистрированных читов зависит от кол-ва комбинаций (в нашем случае четырех клавиш). Поэтому переменной s_maxNumPossibleCheats присваивается значение факториала от 4 (NUM_CHEAT_SEQUENCE_INPUTS = NUM_CHEAT_INPUTS = 4).

Затем выделяется память под массив m_cheats (через аллокатор GMA_PERSISTENT), и каждому элементу этого массива присваивается значение CHEAT_ID_UNREGISTERED (которое равно -1).

Далее идет получение самих ID зарегистрированных читов в зависимости от комбинации клавиш: В метод ConvertSequenceToIndex (переводящий комбинацию клавиш в ID) передается комбинация из четырех клавиш зарегистрированного чита (REGISTERED_CHEATS[ i ].m_cheatInputs (1.1*)), и затем по полученному из метода ConvertSequenceToIndex индексу, некоторому элементу в массиве m_cheats присваивается тип чита текущего рассматриваемого зарегистрированного чита (REGISTERED_CHEATS[i].m_cheatID).

ConvertSequenceToIndex

ConvertSequenceToIndex — метод, переводящий комбинацию клавиш некоторого зарегестрированного чита в его ID.

Скрытый текст

(*) Полный код ConvertSequenceToIndex:

unsigned int
CheatsDB::ConvertSequenceToIndex( const eCheatInput* cheatInputs,
                                  int numInputs )
{
    unsigned int cheatIndex = 0;

    rAssert( cheatInputs != NULL );
    for( int i = 0; i < numInputs; i++ )
    {
        cheatIndex |= (cheatInputs[ i ] << (i * 2));
    }

    rAssertMsg( cheatIndex < s_maxNumPossibleCheats,
                "ERROR: *** Invalid cheat index computed!" );

    return cheatIndex;
}

Способ перевода заключается в том, что так как все ID клавиш это двухбитные числа (0 = 00, 1 = 01, 2 = 10, 3 = 11), и байт, все биты которого равны 1 равняется 255 (примерно факториал от 4) (То есть, при комбинации из четырех клавиш с ID = 3 мы получим ID зарегистрированного чита — 255), то можно записать последовательность ID клавиш в один байт, тем самым получить некоторое значение, которое в последствии будем использовать как ID зарегистрированного чита в массиве m_cheats.

Перевод:

Сначала задаем некоторую переменную типа int, и заполняем все биты этой переменной значением 0. Затем, рассматривая первый байт этой переменной, проходимся по всем парам битов этого байта (по порядку), и записываем в эти биты значения ID клавиш. Таким образом, мы записали все ID клавиш из текущей комбинации и получили число, которое будем использовать в качестве ID зарегистрированного чита в массиве m_cheats. То есть в последствии, при вводе некоторой комбинации можно будет сразу узнать, какой тип чита соответствует данной комбинации.

GetCheatID

GetCheatID — метод, который возвращает тип чита из массива m_cheats по переданному в этот метод ID зарегистрированного чита.

Скрытый текст

(*) Полный код GetCheatID:

eCheatID
CheatsDB::GetCheatID( unsigned int cheatIndex ) const
{
    rAssert( m_cheats );
    rAssert( cheatIndex < s_maxNumPossibleCheats );

    // return cheat ID associated with specified cheat index
    //
    return m_cheats[ cheatIndex ];
}

(1.1*) REGISTERED_CHEATS — массив типа Cheat (1.2*), который хранит всю информацию о зарегистрированных читах, а именно: тип чита, комбинация клавиш для активации этого чита, и его название в виде строки.

Полный список зарегестрированных читов можно найти в файле cheats.cpp:

static const Cheat REGISTERED_CHEATS[] = 
{
    // *** cheat name must be < 32 characters in length *** //
    //
    {
        CHEAT_ID_UNLOCK_VEHICLES,
        { CHEAT_INPUT_0, CHEAT_INPUT_1, CHEAT_INPUT_0, CHEAT_INPUT_1 },
        "Unlock All Reward Vehicles"
    },
    {
        CHEAT_ID_NO_TOP_SPEED,
        { CHEAT_INPUT_2, CHEAT_INPUT_2, CHEAT_INPUT_2, CHEAT_INPUT_2 },
        "No Top Speed"
    },
    {
        CHEAT_ID_HIGH_ACCELERATION, 
        { CHEAT_INPUT_3, CHEAT_INPUT_3, CHEAT_INPUT_3, CHEAT_INPUT_3 },
        "High Acceleration"
    },
    {
        CHEAT_ID_CAR_JUMP_ON_HORN, 
        { CHEAT_INPUT_2, CHEAT_INPUT_2, CHEAT_INPUT_2, CHEAT_INPUT_3 },
        "Car Jump on Horn"
    },
    {
        CHEAT_ID_ONE_TAP_TRAFFIC_DEATH,
        { CHEAT_INPUT_3, CHEAT_INPUT_3, CHEAT_INPUT_2, CHEAT_INPUT_2 },
        "One Tap Traffic Death"
    },
    {
        CHEAT_ID_UNLOCK_CAMERAS,
        { CHEAT_INPUT_1, CHEAT_INPUT_1, CHEAT_INPUT_1, CHEAT_INPUT_0 },
        "Unlock All Cameras"
    },
    {
        CHEAT_ID_PLAY_CREDITS_DIALOG,
        { CHEAT_INPUT_0, CHEAT_INPUT_2, CHEAT_INPUT_2, CHEAT_INPUT_3 },
        "Play Credits Dialog"
    },
    {
        CHEAT_ID_SHOW_SPEEDOMETER,
        { CHEAT_INPUT_3, CHEAT_INPUT_3, CHEAT_INPUT_1, CHEAT_INPUT_2 },
        "Show Speedometer"
    },
    {
        CHEAT_ID_REDBRICK,
        { CHEAT_INPUT_1, CHEAT_INPUT_1, CHEAT_INPUT_3, CHEAT_INPUT_2 },
        "Red Brick"
    },
    {
        CHEAT_ID_INVINCIBLE_CAR,
        { CHEAT_INPUT_3, CHEAT_INPUT_0, CHEAT_INPUT_3, CHEAT_INPUT_0 },
        "Invincible Car"
    },
    {
        CHEAT_ID_SHOW_TREE,
        { CHEAT_INPUT_1, CHEAT_INPUT_0, CHEAT_INPUT_1, CHEAT_INPUT_3 },
        "Show Tree"
    },
    {
        CHEAT_ID_TRIPPY,
        { CHEAT_INPUT_3, CHEAT_INPUT_1, CHEAT_INPUT_3, CHEAT_INPUT_1 },
        "Trippy"
    },

    

#ifndef FINAL
    // register cheats that we don't want shipped in the final game here
    //
    {
        CHEAT_ID_UNLOCK_CARDS,
        { CHEAT_INPUT_0, CHEAT_INPUT_0, CHEAT_INPUT_0, CHEAT_INPUT_0 },
        "Unlock All Collectible Cards"
    },
    {
        CHEAT_ID_UNLOCK_SKINS,
        { CHEAT_INPUT_2, CHEAT_INPUT_3, CHEAT_INPUT_2, CHEAT_INPUT_3 },
        "Unlock All Character Clothing"
    },
    {
        CHEAT_ID_MOTHER_OF_ALL_CHEATS,
        { CHEAT_INPUT_0, CHEAT_INPUT_1, CHEAT_INPUT_2, CHEAT_INPUT_3 },
        "Display All Cheats"
    },
    {
        CHEAT_ID_UNLOCK_MISSIONS,
        { CHEAT_INPUT_0, CHEAT_INPUT_1, CHEAT_INPUT_1, CHEAT_INPUT_0 },
        "Unlock All Missions"
    },
    {
        CHEAT_ID_UNLOCK_MOVIES,
        { CHEAT_INPUT_1, CHEAT_INPUT_3, CHEAT_INPUT_2, CHEAT_INPUT_3 },
        "Unlock All Movies"
    },
    {
        CHEAT_ID_UNLOCK_EVERYTHING,
        { CHEAT_INPUT_0, CHEAT_INPUT_1, CHEAT_INPUT_1, CHEAT_INPUT_3 },
        "Unlock All (Unlockables)"
    },
    {
        CHEAT_ID_EXTRA_COINS,
        { CHEAT_INPUT_0, CHEAT_INPUT_2, CHEAT_INPUT_1, CHEAT_INPUT_3 },
        "Add Extra Coins"
    },
    {
        CHEAT_ID_KICK_TOGGLES_CHARACTER_MODEL,
        { CHEAT_INPUT_1, CHEAT_INPUT_1, CHEAT_INPUT_1, CHEAT_INPUT_2 },
        "Kick Swaps Character Model"
    },
    {
        CHEAT_ID_SHOW_AVATAR_POSITION,
        { CHEAT_INPUT_1, CHEAT_INPUT_1, CHEAT_INPUT_1, CHEAT_INPUT_1 },
        "Show Avatar Position"
    },
    {
        CHEAT_ID_FULL_DAMAGE_TO_CAR,
        { CHEAT_INPUT_2, CHEAT_INPUT_2, CHEAT_INPUT_3, CHEAT_INPUT_3 },
        "Apply Full Damage to Car"
    },
    {
        CHEAT_ID_EXTRA_TIME,
        { CHEAT_INPUT_1, CHEAT_INPUT_1, CHEAT_INPUT_1, CHEAT_INPUT_3 },
        "Extra Objective Time"
    },
    {
        CHEAT_ID_DEMO_TEST,
        { CHEAT_INPUT_0, CHEAT_INPUT_0, CHEAT_INPUT_0, CHEAT_INPUT_1 },
        "Enable Demotest mode"
    },
#endif

    // dummy terminator
    //
    {
        CHEAT_ID_UNREGISTERED,
        { UNKNOWN_CHEAT_INPUT, UNKNOWN_CHEAT_INPUT, UNKNOWN_CHEAT_INPUT, UNKNOWN_CHEAT_INPUT },
        ""
    }
};

(1.2*) Структуру Cheat можно найти в файле cheats.h:

struct Cheat
{
    eCheatID m_cheatID;
    eCheatInput m_cheatInputs[ NUM_CHEAT_SEQUENCE_INPUTS ];
    const char* m_cheatName;
};

eCheatID — тип чита;
eCheatInput (1.3*);
m_cheatName — наименование чита.

(1.3*) Каждой клавише, так или иначе связанной с чит-кодами, представлен некоторый ID (диапазон значений этого ID лежит от 0 до 3 включительно).

eCheatInput — перечисление, которое содержит все ID клавиш (CHEAT_INPUT):

enum eCheatInput
{
    UNKNOWN_CHEAT_INPUT = -1,

    CHEAT_INPUT_0,
    CHEAT_INPUT_1,
    CHEAT_INPUT_2,
    CHEAT_INPUT_3,

    NUM_CHEAT_INPUTS
};

Полный список соответствия клавиш и их ID (в зависимости от платформы, на которой запущена игра) представлен в виде массива в файле cheatinputhandler.cpp:

static const CheatInputMapping CHEAT_INPUT_MAPPINGS[] =
{
#ifdef RAD_GAMECUBE
    { "A",              CHEAT_INPUT_0 },
    { "B",              CHEAT_INPUT_1 },
    { "X",              CHEAT_INPUT_2 },
    { "Y",              CHEAT_INPUT_3 },
    { "TriggerL",       CHEAT_INPUT_LTRIGGER },
    { "TriggerR",       CHEAT_INPUT_RTRIGGER },
#endif

#ifdef RAD_PS2
    { "X",              CHEAT_INPUT_0 },
    { "Circle",         CHEAT_INPUT_1 },
    { "Square",         CHEAT_INPUT_2 },
    { "Triangle",       CHEAT_INPUT_3 },
    { "L1",             CHEAT_INPUT_LTRIGGER },
    { "R1",             CHEAT_INPUT_RTRIGGER },
#endif

#ifdef RAD_XBOX
    { "A",              CHEAT_INPUT_0 },
    { "B",              CHEAT_INPUT_1 },
    { "X",              CHEAT_INPUT_2 },
    { "Y",              CHEAT_INPUT_3 },
    { "LeftTrigger",    CHEAT_INPUT_LTRIGGER },
    { "RightTrigger",   CHEAT_INPUT_RTRIGGER },
#endif

#ifdef RAD_WIN32        // these are not laid out yet
    { "Attack",         CHEAT_INPUT_0 },
    { "Jump",           CHEAT_INPUT_1 },
    { "Sprint",         CHEAT_INPUT_2 },
    { "DoAction",       CHEAT_INPUT_3 },
    { "CameraFunc1",    CHEAT_INPUT_LTRIGGER },
    { "CameraFunc2",    CHEAT_INPUT_RTRIGGER },
#endif

    { "",               UNKNOWN_CHEAT_INPUT }
};

Часть 2 (Ввод комбинации)

Ввод комбинации из 4 клавиш производится в методе OnButtonDown файла cheatinputhandler.cpp.

Скрытый текст

(*) Полный код OnButtonDown:

void CheatInputHandler::OnButtonDown( int controllerId,
                                      int buttonId,
                                      const IButton* pButton )
{
    switch( buttonId )
    {
        case CHEAT_INPUT_LTRIGGER:
        {
            m_LTriggerBitMask |= (1 << controllerId);

            bool isRTriggerDown = ((m_RTriggerBitMask & (1 << controllerId)) > 0);
            GetCheatInputSystem()->SetActivated( controllerId,
                                                 isRTriggerDown );

            break;
        }
        case CHEAT_INPUT_RTRIGGER:
        {
            m_RTriggerBitMask |= (1 << controllerId);

            bool isLTriggerDown = ((m_LTriggerBitMask & (1 << controllerId)) > 0);
            GetCheatInputSystem()->SetActivated( controllerId,
                                                 isLTriggerDown );

            break;
        }
        default:
        {
            if( GetCheatInputSystem()->IsActivated( controllerId ) )
            {
                rAssert( buttonId < NUM_CHEAT_INPUTS );

                rReleasePrintf( "Received Cheat Input [%d] = [%d]\n",
                                m_currentInputIndex, buttonId );

                m_inputSequence[ m_currentInputIndex++ ] = static_cast( buttonId );
                m_currentInputIndex %= NUM_CHEAT_SEQUENCE_INPUTS;

                if( m_currentInputIndex == 0 )
                {
                    GetCheatInputSystem()->ReceiveInputs( m_inputSequence );
                }
            }

            break;
        }
    }
}

ID клавиши кастится к eCheatInput и передается в массив m_inputSequence, который хранит ID нажатых клавиш (этот массив вмещает в себя всего 4 элемента). Если текущий указатель на элемент массива m_currentInputIndex равен 4, то он аннулируется. Если указатель m_currentInputIndex указывает на нулевой элемент массива m_inputSequence, то в нашем массиве собралась полная комбинация из четырех клавиш.

Скрытый текст

(*) После перезахода в меню, откуда производится сам ввод клавиш для чит-кодов, элементы массива m_inputSequence заполняются значениями UNKNOWN_CHEAT_INPUT (UNKNOWN_CHEAT_INPUT = -1), а указатель m_currentInputIndex начинает указывать на нулевой элемент данного массива.

Часть 3 (CheatInputSystem — Обработка комбинации)

После ввода комбинации клавиш в методе OnButtonDown, вызывается метод ReceiveInputs, где и происходит обработка введенной комбинации клавиш.

Скрытый текст

(*) Полный код ReceiveInput:

void
CheatInputSystem::ReceiveInputs( eCheatInput* cheatInputs,
                                 int numInputs )
{
    int cheatIndex = CheatsDB::ConvertSequenceToIndex( cheatInputs,
                                                       numInputs );

    // get cheatID associated with received input sequence
    //
    rAssert( m_cheatsDB );
    eCheatID cheatID = m_cheatsDB->GetCheatID( cheatIndex );

    // validate cheatID
    //
    if( cheatID != CHEAT_ID_UNREGISTERED )
    {
        // Yay! Successful cheat code entered!!
        //
#ifdef FINAL
        // TC: toggling cheats on/off isn't really supported for all cheats, so
        //     letz just not allow this in the final build
        // 
        bool isCheatEnabled = true;
#else
        bool isCheatEnabled = !this->IsCheatEnabled( cheatID ); // toggle
#endif

        this->SetCheatEnabled( cheatID, isCheatEnabled );

        if( this->IsCheatEnabled( cheatID ) )
        {
            GetEventManager()->TriggerEvent( EVENT_FE_CHEAT_SUCCESS );
        }
        else
        {
            GetEventManager()->TriggerEvent( EVENT_FE_CHEAT_FAILURE );

            isCheatEnabled = false;

            rReleasePrintf( "*** This cheat cannot be enabled until all story missions have been completed!\n" );
        }

        rReleasePrintf( "*** Cheat code successfully entered: %s (%s)\n",
                        m_cheatsDB->GetCheat( cheatID )->m_cheatName,
                        isCheatEnabled ? "enabled" : "disabled" );

        // if this is the "unlock everything" cheat, then unlock everything (duh...)
        //
        if( cheatID == CHEAT_ID_UNLOCK_EVERYTHING )
        {
            for( int unlockCheatID = CHEAT_ID_UNLOCK_BEGIN;
                 unlockCheatID < CHEAT_ID_UNLOCK_EVERYTHING;
                 unlockCheatID++ )
            {
                this->SetCheatEnabled( static_cast( unlockCheatID ), isCheatEnabled );
            }
        }
        else if ( cheatID == CHEAT_ID_DEMO_TEST )
        {
            GetGuiSystem()->GotoScreen( CGuiWindow::GUI_SCREEN_ID_SPLASH, 0, 0, CLEAR_WINDOW_HISTORY );
        }
    }
    else
    {
        // Booo.... try again!!
        //
        GetEventManager()->TriggerEvent( EVENT_FE_CHEAT_FAILURE );

        rReleasePrintf( "*** Invalid cheat code entered!\n" );
    }
}

Сначала, по полученной комбинации получаем ID зарегистрированного чита путем вызова метода ConvertSequenceToIndex. Затем, по этому ID получаем тип зарегистрированного чита вызовом метода GetCheatID.

Далее идет проверка на то, привязан ли хоть один чит к полученной комбинации (не равняется ли полученный тип чита CHEAT_ID_UNREGISTERED). Если ни один чит не привязан к проверяемой комбинации, то эта попытка логгируется и происходит выход из функции. Иначе, мы вызываем метод SetCheatEnabled (3.1*), который записывает информацию о том, что чит активирован или выключен.

При вызове метода SetCheatEnabled вызывается метод OnCheatEntered (3.3*), который и оказывает влияние на игровые объекты, поддерживающие чит-коды.

(3.1*) SetCheatEnabled — метод, который побитово записывает в переменную s_cheatsEnabled информацию об активности некоторого чита (если данный чит еще не активен, то он активируется. Иначе чит выключается).

s_cheatsEnabled — переменная типа int, которая используется для отслеживания активности читов (Так как переменная имеет тип int, то всего под эту переменную выделено 32 бита (в зависимости от платформы). Исходя из этого можно сделать вывод о том, что максимально возможное кол-во зарегистрированных читов равно 32).

Полный код SetCheatEnabled:

void
CheatInputSystem::SetCheatEnabled( eCheatID cheatID, bool enable )
{
#ifdef FINAL
    if( cheatID == CHEAT_ID_UNLOCK_CARDS ||
        cheatID == CHEAT_ID_UNLOCK_SKINS ||
        cheatID == CHEAT_ID_UNLOCK_VEHICLES )
    {
        if( !GetCharacterSheetManager()->IsAllStoryMissionsCompleted() )
        {
            return;
        }
    }
#endif

    if( enable )
    {
        s_cheatsEnabled |= (1 << cheatID);
    }
    else
    {
        s_cheatsEnabled &= ~(1 << cheatID);
    }

    // notify registered clients that valid cheat code has been entered
    //
    for( int i = 0; i < m_numClientCallbacks; i++ )
    {
        rAssert( m_clientCallbacks[ i ] ); // (3.4*)
        m_clientCallbacks[ i ]->OnCheatEntered( cheatID, enable );
    }
}

Также можно заметить, что для активации читов CHEAT_ID_UNLOCK_CARDS, CHEAT_ID_UNLOCK_SKINS и CHEAT_ID_UNLOCK_VEHICLES нужно завершить все сюжетные миссии.

(3.2*) IsCheatEnabled — метод, который берет информацию из переменной s_cheatsEnabled об активности переданного в эту функцию типа чита, и возвращает ее.

Полный код IsCheatEnabled:

bool
CheatInputSystem::IsCheatEnabled( eCheatID cheatID ) const
{
    return ((s_cheatsEnabled & (1 << cheatID)) > 0);
}

(3.3*) OnCheatEntered — метод класса ICheatEnteredCallback, который реализует включение/выключение некоторого чит-кода для класса, который наследуется от ICheatEnteredCallback.

ICheatEnteredCallback — класс, который является базовым интерфейсом для всех игровых объектов, поддерживающих использование чит-кодов (То есть все классы, на которых действуют чит-коды, наследуются от интерфейса ICheatEnteredCallback).

Интерфейс ICheatEnteredCallback:

struct ICheatEnteredCallback
{
    virtual void OnCheatEntered( eCheatID cheatID, bool isEnabled ) = 0;
};

(3.4*) RegisterCallback — метод, который добавляет переданный указатель на класс, наследующийся от ICheatEnteredCallback (то есть поддерживающий чит-коды), в массив m_clientCallbacks.

Обычно RegisterCallback вызывается в конструкторах классов, наследуемых от ICheatEnteredCallback.

Полный код RegisterCallback:

void
CheatInputSystem::RegisterCallback( ICheatEnteredCallback* callback )
{
    rAssert( callback != NULL );

    // first, check if callback is already registered
    //
    for( int i = 0; i < m_numClientCallbacks; i++ )
    {
        if( m_clientCallbacks[ i ] == callback )
        {
            // yup, ignore redundant request
            return;
        }
    }

    rAssert( static_cast( m_numClientCallbacks ) < 
             sizeof( m_clientCallbacks ) / sizeof( m_clientCallbacks[ 0 ] ) );

    // add new registered callback
    //
    m_clientCallbacks[ m_numClientCallbacks ] = callback;
    m_numClientCallbacks++;
}

Часть 4 (Добавление своего чит-кода в игру)

Итого мы теперь знаем, что нужно сделать для добавления своих чит-кодов в игру:

  1. Добавить новый тип чита в eCheatID;

  2. Прикрепить некоторую комбинацию клавиш к добавленному читу в массиве REGISTERED_CHEATS;

  3. Добавить некоторый класс, который будет реагировать на наш чит, в массив m_clientCallbacks через метод RegisterCallback;

  4. Добавить обработку нашего чита через реализацию метода OnCheatEntered в этом классе.

Заключение

Вот и всё. Мы рассмотрели внутреннее устройство системы чит-кодов и, что не маловажно, реализацию своих чит-кодов.

Надеюсь, данный материал был вам полезен.

Мою прошлую статью про аллокаторы в The Simpsons: Hit & Run 2003 можете найти среди моих прошлых публикаций.

Полный исходный код игры The Simpsons: Hit & Run 2003 можно посмотреть здесь:

GitHub — FlexodMR/Simpsons: Original source for «The Simpsons: Hit & Run» game

github.com

© Habrahabr.ru