Решил поделиться своим подходом. Я не люблю скриптить, поэтому решил вычистить под ноль все скрипты и использовать опкоды непосредственно в с++. В будущем надеюсь заменить опкоды на функции, насколько возможно.
Это запуск игры без сриптов, если можно так сказать, где определены координаты игрока и расставлено оружие средствами с++:
Вот мой main.scm, здесь осталось только создание игрока, потому что я не смог это перенести в плагин:
DEFINE VERSION VICE 1.5
0002: jump гг_HEAD
DEFINE MEMORY 34329
:_HEAD
0002: jump гг_MAIN
DEFINE MISSIONS 0
:_MAIN
016A: fade 0 0 ms
0053: $PLAYER_CHAR = create_player #NULL at 0.0 0.0 0.0
01F5: $PLAYER_ACTOR = create_emulated_actor_from_player $PLAYER_CHAR
0001: wait 0 ms
016A: fade 1 (back) 1000 ms
:_WAIT
0001: wait 10000 ms
0002: jump гг_WAIT
После этого у нас создан указатель на игрока. Со всем остальным мы можем работать в плагине.
Подключаем к своему проекту исходники спидометра Spookie, в частности GameScripting.cpp и GameScripting.h. Там уже есть примеры опкодов, я определил свои для минимально необходимого запуска игры:
const SCRIPT_COMMAND set_camera = { 0x03CB, "fff" }; // x, y, z
const SCRIPT_COMMAND refresh_game_renderer = { 0x04E4, "ff" }; // x,y
const SCRIPT_COMMAND set_player_angle = { 0x0171, "vf" }; // PLAYER_CHAR pointer, angle
const SCRIPT_COMMAND set_camera_behind_player = { 0x0373, "" }; //
const SCRIPT_COMMAND set_current_time = { 0x00C0, "ii" }; // Hours, Minutes
const SCRIPT_COMMAND create_weapon_pickup = { 0x032B, "iiifffv" }; // id, type, ammo, x, y, z, pickup pointer
Адрес в фигурных скобках это и есть опкод, дальше идет форматная строка типов параметров опкода, думаю из примеров легко понять как их ставить.
Ну и теперь можно скриптить что угодно в своем плагине, используя весь функционал с++, чтение настроек из файлов, классы, массивы и т.д.
Вот пример начала игры с расставленными пикапами оружия, добавьте к этому припаркованные авто, плотность траффика, локации банд и прочее и получите полноценную игру.
Единственный открытый вопрос - это как писать миссии при таком подходе и работать с сэйвами, но для глобальных модов, где обычно используется чистый мэйн это неактуально. Можно придумать свой механизм управления миссиями средствами с++.
#include "stdafx.h"
#include "GameScripting.h"
bool NewGame = true;
DWORD *dwPlayerChar = (DWORD *) 0x821288;
DWORD *dwPlayerActor = (DWORD *) 0x82128C;
DWORD *dwPlayer = (DWORD *) 0x94AD28;
//--------------------------------------------------------------------------------
//
void TestPlayer()
{
if (*dwPlayer)
{
if (NewGame) // Опкоды при старте игры
{
NewGame = false;
BYTE *bPlayerBlock = (BYTE*)(*dwPlayer);
float *X = (float*)(bPlayerBlock+0x34);
float *Y = (float*)(bPlayerBlock+0x38);
float *Z = (float*)(bPlayerBlock+0x3C);
*X = 155.0f; *Y = -510.0f; *Z = 13.0f;
ScriptCommand(&set_camera, *X, *Y, *Z);
ScriptCommand(&refresh_game_renderer,*X, *Y);
ScriptCommand(&set_player_angle,dwPlayerChar, 230.0f);
ScriptCommand(&set_camera_behind_player);
ScriptCommand(&set_current_time,10,0);
for (int i=0;i<10;i++)
{
DWORD generated;
ScriptCommand(&create_weapon_pickup,274+i,2,100,*X+8-i*0.1f,*Y-2*i,*Z,&generated);
}
}
// Тут любые опкоды игрового процесса, выполняющиеся в цикле по времени
}
else NewGame = true;
};
//--------------------------------------------------------------------------------
//
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
SetTimer(0,0, 10, (TIMERPROC)TestPlayer);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}