Добавление Adobe Flash
РУССКАЯ ВЕРСИЯ СТАТЬИ В РАЗРАБОТКЕ!
Введение
Adobe Flash - это мощный и универсальный инструмент для разработки пользовательских интерфейсов. Во многих современных играх AAA используют Adobe Flash для создания пользовательского интерфейса; среди этих игр - Crysis 2, Dead Space 2, Mass Effect 2 и Counter-Strike: Global Offensive. Из этого туториала вы узнаете, как настроить собственный интерфейс Adobe Flash для его использования с Source SDK. Однако этот метод потребует небольших исправлений исходного кода.
Помните, что интерфейс, который мы будем реализовывать, не оптимизирован для игр. Однако, используя тот же способ, можно так же легко использовать профессиональное Flash-решение, такое как Autodesk Scaleform, или веб-интерфейс, такой как Awesomium. Также имейте в виду, что код, который мы напишем здесь, не соответствует стандартам игровой индустрии; если вы это используете для профессионального мода, вы можете повеселиться, всячески исправляя исходный код.
Пример использования Adobe Flash в Source SDK мы можете найти здесь: Flash Implementation Video
Предисловия
Сначала вам нужно будет скачать копию FlashDX. Flash-To-DirectX действительно поможет ускорить процесс внедрения пользовательских Flash-интерфейсов. Имейте в виду, что FlashDX содержит только COM-компонент Flash; Это решение не оптимизировано для игр и значительно снизит частоту кадров при использовании сложных flash-файлов. Если скорость и совместимость для вас на первом месте, тщательно изучите способ взлома, но используйте Scaleform или нечто подобное вместо FlashDX.
Затем загрузите версию DirectX SDK, выпущенную в июне 2010 года. В более новых версиях может не хватать некоторых фишек, поэтому, чтобы избавиться от ошибок при отладке, установите версию 2010 года выпуска.
Начинаем
Я немного изменил свою копию Flash DX; Я избавился от механизма прозрачности и заменил его собственным алгоритмом написания цветных ключей, также я написал метод для рендеринга Flash-объектов в сетке и написал метод для создания спроецированной флэш-матрицы. Однако для данного руководства нам вполне хватит немодифицированной версии FlashDX. Скомпилируйте FlashDX в многопоточную (/ MT) статическую библиотеку (в режиме выпуска). Если вы не измените тип потоков, то компоновщик выдаст много странных ошибок.
Затем переключитесь на ваш мод Source SDK. Перейдите в «Проект» > «Свойства клиентского SDK» (лично я использую Visual Studio 2012; аналогичным образом все работает в VS 2010 - просто щелкните правой кнопкой мыши фильтр проекта Client SDK и выберите «Свойства»). В дереве выберите узел «Каталоги VC++». Добавьте библиотеки FlashDX и DirectX SDK и включите их в свои каталоги VC++. Надеюсь, мне не нужно вам по шагам объяснять, как это сделать.
The Client-side Code
Once you have everything linked, you are now ready to start coding. I wrote a few basic classes to get you started, but I suggest writing your own if you need more control; the important thing here is the hack to get the whole Flash system working. I come from a C# .NET background so the way I name variables may be different. I occasionally try to use C++ naming conventions too.
Anyway, start by creating two new files in your Client project: FlashManager.h and FlashManager.cpp
We're going to need a few includes for the Flash Manager class that we are about to create. The class will allow us to load SWF files, catch Flash commands from a custom env_flash map entity, and allow us to play, scale, and render Flash frames. Begin the FlashManager.h file with the following:
#pragma once
#include <Windows.h>
#include <TCHAR.h>
#include <Shlwapi.h>
#include <d3d9.h>
#include <d3dx9.h>
#include <d3dx9math.h>
#include <d3dx9mesh.h>
#include <ddraw.h>
#include <string>
#include <vector>
#include "IFlashDX.h"
#include "ASInterface.h"
#include <GameEventListener.h>
You may have noticed that the implementation is highly Windows based right from the get go with #include <Windows.h>
. This is only on the client side. If cross-platform compatibility is very important to you, then I'll leave you the work of finding *nix equivalents of various Windows functions. Note that you will also have to find something other than FlashDX as well (look into GameSWF). In this tutorial, I assume you are writing a Source SDK mod for the Windows OS. The server project remains cross-platform so that we may build *.so libraries for dedicated Linux servers in the future.
Next, we need a method of storing Flash movie information. Let's create a struct
to store basic SWF movie information.
// This object acts as the Flash Container by loading SWF files and rendering textures
struct FlashMovie
{
private:
int m_height, m_width, m_x, m_y;
std::string m_path;
bool init;
public:
// Creates a FlashMovie container object. If Width and Height are not specified, the game window's width and height are used.
FlashMovie(std::string SWFPath, int Width = -1, int Height = -1, int screen_x = 0, int screen_y = 0);
// Destroys and unloads the Flash movie
~FlashMovie();
// Returns the flash movie absolute file path
std::string GetPath();
// Gets the user-defined flash movie width
int GetWidth();
// Gets the user-defined flash movie height
int GetHeight();
// Returns the x and y coordinates of the default screen position.
std::pair<int, int> GetScreenPosition();
// Sets the position of the movie on the screen
void SetScreenPosition(uint x, uint y);
// -----------------------------------------------------------------
// The following in general should not be used by the end user.
// -----------------------------------------------------------------
// Resizes the FlashMovie to the specified width and height given a IFlashDXPlayer pointer. Use FlashManager's ResizeMovie instead of this.
void ResizeMovie(int Width, int Height, IFlashDXPlayer* m_flashPlayer);
};
Next, we need a way of managing multiple SWF files. For example, we may have separate SWF files for the title screen, in-game UI, server browser, etc. To manage them, we would need to create a FlashManager class:
class FlashManager : public CGameEventListener
{
private:
// Managment objects
std::vector<FlashMovie> m_movieArray;
int playingFlashFile, lastFlashIndex, w, h;
bool recreatedTargets;
bool enableInputProcessing;
bool screenBlend;
// D3D classes
D3DXVECTOR2 Translation;
D3DXMATRIX Mat;
D3DXVECTOR2 Scaling;
LPD3DXSPRITE Sprite;
D3DSURFACE_DESC Desc;
std::pair<int,int> currentPos;
// Flash objects
IFlashDX* m_flashDX;
IFlashDXPlayer* m_flashPlayer;
ASInterface* m_playerASI;
private:
bool bCompare(const BYTE* pData, const BYTE* bMask, const char* szMask);
DWORD FindPattern(DWORD dwAddress, DWORD dwLen, BYTE* bMask,char* szMask);
void* DetourFunction(BYTE* source, const BYTE* destination, const int length);
public:
FlashManager();
~FlashManager();
// Adds a flash movie to the list and returns the index. Returns -1 if the renderer has not loaded completely.
int AddFlashMovie(FlashMovie fl_movie);
// Plays a flash movie provided an index. Use with AddFlashMovie()
void PlayFlashMovie(int index);
// Returns the index of the current movie that is playing, or -1 if no movie is playing.
int GetPlayingMovieIndex();
// Returns true if a flash movie is currently playing.
bool IsPlayingMovie();
// Hooks an ActionScript FSCommand with your function. That is, whenever fscommand() is called in flash, that FSCommand is registered with your c++ function.
void HookFSCommand(std::string, void (*)(const wchar_t*));
// Resizes a FlashMovie given the flash movie index.
void ResizeMovie(int index, int Width, int Height);
// Forces the playing flash movie to skip to the specified frame.
void ChangeFrame(int frame);
// Gets the rendered screen location of the flash movie.
std::pair<int,int> GetCurrentPos();
// Enables or disables input processing through the hijacked message pump. In other words, if input processing is disabled, every mouse/keyboard input
// is sent only to Flash instead of both the game and Flash.
void SetGameInputProcessing(bool enableInput);
// Returns true if the game's input systems are processing.
bool IsGameInputProcessing();
// Unloads the flash movie and resets flash-related variables.
void StopPlayingMovie();
// Returns the last played movie's index
int GetLastIndex();
// Returns the index of the specified movie if it has already been added, or -1 if it has not
int IsMovieInList(const char* filename);
// Sets blend mode (true = photoshop-like Screen filter, false = opaque)
void SetScreenBlending(bool enableScreen);
// Returns the scale factor
D3DXVECTOR2 GetScale();
// -----------------------------------------------------------------
// The following in general should not be used by the programmer.
// -----------------------------------------------------------------
// Returns true if the Flash manager has initialized all of its variables and has attained a pointer to the hacked D3D device
bool HasRecreatedTargets();
// Recreates D3D targets and assigns the d3d device pointer through the vtable hack
bool RecreateTargets(IDirect3DDevice9* pD3DDevice);
// Renders the flash movie onto the screen utilizing the direct 3d device pointer. Note: do not use, as it is used internally.
void SceneDataHook();
// Returns the d3d device pointer (in case you need it for your other projects).
IDirect3DDevice9* GetD3DDevice();
// Returns the IFlashDXPlayer pointer to utilize FlashDX manually.
IFlashDXPlayer* GetFlashPlayer();
// Fires a flash related event (either render_flash_hud or flash_hud_toggle)
void FireGameEvent(IGameEvent* event);
};
Alright, so we got the header stuff down. If the fact that I put more than one class into a header file annoys you, you can split up the classes into different headers. It's more fun to keep things simple and put both relevant classes into one header.
Next, it's time to go into the meat of the code. Add the following includes to your FlashManager.cpp. Note that I may have some extra headers because I coded a deeper implementation than the one covered in this tutorial.
#include "cbase.h"
#include "FlashManager.h"
#include <Windows.h>
#include <TCHAR.h>
#include <Shlwapi.h>
#include <d3d9.h>
#include <d3dx9.h>
#include <d3dx9math.h>
#include <d3dx9mesh.h>
#include <ddraw.h>
#include <string>
#include <vector>
#include "ienginevgui.h"
#include <vgui/isurface.h>
#include <vgui/IVGui.h>
#include <vgui/IInput.h>
#include <vgui_controls/Panel.h>
#include <tier3/tier3.h>
Next, some global variables, defines, and functions:
// This will store the HWND of the game window later
HWND hwndMain;
// Some macros to simply things later
#ifndef MSGBOX_FLASH
#define MSGBOX(text) MessageBoxA(hwndMain, text, "Mutation:Source", MB_OK);
#endif
#ifndef MSGBOX_NULL
#define MSGBOXN(text) MessageBoxA(NULL, text, "Mutation:Source", MB_OK);
#endif
#ifndef GET_X_LPARAM
#define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp))
#endif
#ifndef GET_Y_LPARAM
#define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp))
#endif
// Displays a MessageBox
void msgf(char* fmt, ...)
{
va_list args;
va_start(args, fmt);
char str_buffer[512];
vsprintf(str_buffer, fmt, args);
MSGBOX(str_buffer);
va_end(args);
}
// Hooks
HRESULT APIENTRY d3dSceneHook(LPDIRECT3DDEVICE9 pDevice);
HRESULT (APIENTRY *SceneObject)(LPDIRECT3DDEVICE9 pDevice);
// Variables for WndProc hijacking
WNDPROC OldWndProc;
void InitWinProcHook();
LRESULT CALLBACK myWndProcHook(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
// Flash and other D3D globals; would be nice to have all these variables in one place at the top
FlashManager* g_FlashManager = NULL;
IDirect3DTexture9* g_Texture = NULL;
IDirect3DDevice9* g_pD3DDevice = NULL;
// A small helper function to convert a string to a wide string
std::wstring StringToWString(const std::string& s)
{
std::wstring temp(s.length(),L' ');
std::copy(s.begin(), s.end(), temp.begin());
return temp;
}
// Flash and D3D texture related vars
const IFlashDXPlayer::ETransparencyMode transparency_mode = IFlashDXPlayer::TMODE_OPAQUE;
const int num_textures_in_rotation = 2;
IDirect3DTexture9* g_texturesRotation[num_textures_in_rotation] = { NULL };
int g_currentTexture = 0;
The MSGBOX_FLASH
and MSGBOX_NULL
macros are there for debugging purposes. You can remove the references to these macros if you so desire.
Alrightie then, we have now (messily) defined our global variables and functions. Feel free to organize them for clarity. Next, it's time to define the various FlashMovie functions. Note that our FlashMovie struct is solely there to store SWF information.
FlashMovie::FlashMovie(std::string SWFPath, int Width, int Height, int screen_x, int screen_y)
{
int w, h; engine->GetScreenSize(w, h);
if (Width == -1) Width = w;
if (Height == -1) Height = h;
SWFPath = SWFPath.insert(0, "\\");
SWFPath = SWFPath.insert(0, engine->GetGameDirectory());
char* sz = const_cast<char*>(SWFPath.c_str());
m_width = Width;
m_height = Height;
m_path = SWFPath;
m_x = screen_x;
m_y = screen_y;
init = false;
}
std::string FlashMovie::GetPath()
{
return m_path;
}
int FlashMovie::GetWidth()
{
return m_width;
}
int FlashMovie::GetHeight()
{
return m_height;
}
FlashMovie::~FlashMovie()
{
}
std::pair<int, int> FlashMovie::GetScreenPosition()
{
return std::pair<int, int>(m_x, m_y);
}
void FlashMovie::ResizeMovie(int Width, int Height, IFlashDXPlayer* m_flashPlayer)
{
int w = -1, h = -1;
engine->GetScreenSize(w, h);
if (Width == -1) Width = w;
if (Height == -1) Height = h;
m_width = Width;
m_height = Height;
if (m_flashPlayer)
m_flashPlayer->ResizePlayer(Width, Height);
}
void FlashMovie::SetScreenPosition(uint x, uint y)
{
m_x = x;
m_y = y;
}
The FlashManager class will manage FlashMovie objects and also do the hacking. It would be much more efficient to separate the hacking functions and methods to a different class (so they can be reused for other purposes), but for this tutorial, I will stick them into the FlashManager class for simplicity. Just keep in mind that we will end up creating a very amateurish implementation-- on the bright side, it's easy to understand.
// Don't worry about how these functions work. I wish I knew who made them-- I found them over at MPGH
bool FlashManager::bCompare(const BYTE* pData, const BYTE* bMask, const char* szMask)
{
for(;*szMask;++szMask,++pData,++bMask)
if(*szMask=='x' && *pData!=*bMask )
return false;
return (*szMask) == NULL;
}
DWORD FlashManager::FindPattern(DWORD dwAddress, DWORD dwLen, BYTE* bMask,char* szMask)
{
for(DWORD i=0; i < dwLen; i++)
if( bCompare( (BYTE*)( dwAddress+i ),bMask,szMask) )
return (DWORD)(dwAddress+i);
return 0;
}
void* FlashManager::DetourFunction(BYTE* source, const BYTE* destination, const int length)
{
BYTE* jmp = (BYTE*)malloc(length + 5);
DWORD dwBack;
VirtualProtect(source, length, PAGE_EXECUTE_READWRITE, &dwBack);
memcpy(jmp, source, length);
jmp += length;
jmp[0] = 0xE9;
*(DWORD *)(jmp + 1) = (DWORD)(source + length - jmp) - 5;
source[0] = 0xE9;
*(DWORD*)(source + 1) = (DWORD)(destination - source) - 5;
for(int i = 5; i < length; i++)
{
source[i] = 0x90;
}
VirtualProtect(source, length, dwBack, &dwBack);
return (jmp - length);
}
When the FlashManager object is created, we will initiate the hack. This is a bad way to go about it, but it works for our purposes.
FlashManager::FlashManager()
{
engine->GetScreenSize(w, h);
// Flash init
m_flashDX = GetFlashToDirectXInstance();
m_flashPlayer = m_flashDX->CreatePlayer(w, h);
if (!m_flashPlayer)
{
MessageBox(NULL, "Flash Player failed to initialize.", "Error", MB_OK);
abort();
}
m_playerASI = new ASInterface(m_flashPlayer);
// DirectX hack
DWORD ES_Address;
DWORD* vtbl = 0;
DWORD table = FindPattern((DWORD)GetModuleHandle("d3d9.dll"), 0x128000, (PBYTE)"\xC7\x06\x00\x00\x00\x00\x89\x86\x00\x00\x00\x00\x89\x86", "xx????xx????xx");
memcpy(&vtbl, (void*)(table+2), 4);
ES_Address = vtbl[42];
if (ES_Address)
SceneObject = (HRESULT (WINAPI *)(LPDIRECT3DDEVICE9 pDevice))(DetourFunction((PBYTE)ES_Address, (PBYTE)d3dSceneHook, 5));
else
{
MSGBOX("Error: Could not detour D3D9 EndScene() hook which is needed for Flash initialization.");
abort();
}
// Set up default matrix
Translation.x = 0;
Translation.y = 0;
currentPos.first = 0;
currentPos.second = 0;
Scaling.x = 1.0f;
Scaling.y = 1.0f;
D3DXMatrixTransformation2D(&Mat, NULL, 0, &Scaling, NULL, 0, &Translation);
// Flash manager data
playingFlashFile = -1;
lastFlashIndex = -1;
recreatedTargets = false;
screenBlend = false;
// Flash events
ListenForGameEvent("flash_hud_toggle");
ListenForGameEvent("render_flash_hud");
// WinProc hack
enableInputProcessing = true;
InitWinProcHook();
}
Later, in the server section, we will create a env_flash
entity. In order for the flash manager to communicate with the flash entity, we will need to capture networked events.
void FlashManager::FireGameEvent(IGameEvent* event)
{
const char* eventName = event->GetName();
if (FStrEq(eventName, "flash_hud_toggle"))
{
if (event->GetBool("enable"))
PlayFlashMovie(GetLastIndex());
else
StopPlayingMovie();
}
else if (FStrEq(eventName, "render_flash_hud"))
{
int flashIndex = -1;
if ((flashIndex = g_FlashManager->IsMovieInList(event->GetString("file"))) == -1)
{
FlashMovie renderMovie(event->GetString("file"), event->GetInt("width"), event->GetInt("height"), event->GetInt("x"), event->GetInt("y"));
flashIndex = g_FlashManager->AddFlashMovie(renderMovie);
}
else
{
// Update the old movie parameters
if (m_movieArray[flashIndex].GetHeight() != event->GetInt("height") || m_movieArray[flashIndex].GetWidth() != event->GetInt("width"))
ResizeMovie(flashIndex, event->GetInt("width"), event->GetInt("height"));
m_movieArray[flashIndex].SetScreenPosition(event->GetInt("x"), event->GetInt("y"));
}
SetScreenBlending(event->GetBool("blend"));
PlayFlashMovie(flashIndex);
}
}
Because the client DLL loads long after DirectX is initialized, we will need to set up our DirectX variables mid-render.
bool FlashManager::RecreateTargets(IDirect3DDevice9* pD3DDevice)
{
HRESULT hr;
int movieIndex = GetPlayingMovieIndex();
int newWidth = -1, newHeight = -1;
if (movieIndex > -1)
{
newWidth = m_movieArray[movieIndex].GetWidth();
newHeight = m_movieArray[movieIndex].GetHeight();
}
else
{
newWidth = w;
newHeight = h;
}
hr = pD3DDevice->CreateTexture(newWidth, newHeight, 1, 0, transparency_mode ? D3DFMT_A8R8G8B8 : D3DFMT_X8R8G8B8, D3DPOOL_DEFAULT, &g_Texture, NULL);
if (FAILED(hr))
return false;
D3DXCreateSprite(pD3DDevice, &Sprite);
g_pD3DDevice = pD3DDevice;
if (m_flashPlayer)
m_flashPlayer->ResizePlayer(newWidth, newHeight);
for (int i = 0; i < num_textures_in_rotation; ++i)
{
hr = pD3DDevice->CreateTexture(newWidth, newHeight, 1, 0,
transparency_mode ? D3DFMT_A8R8G8B8 : D3DFMT_X8R8G8B8, D3DPOOL_SYSTEMMEM, &g_texturesRotation[i], NULL);
if (FAILED(hr))
return false;
}
recreatedTargets = true;
return true;
}
The following is the code that draws our Flash file as a sprite on the screen.
void FlashManager::SceneDataHook()
{
if (!HasRecreatedTargets())
{
assert("Tried to use SceneDataHook without initialization of texture buffer");
return;
}
if (!IsPlayingMovie())
return;
HRESULT hr;
// Update flash movie as necessary
unsigned int numDirtyRects; const RECT* dirtyRects;
if (m_flashPlayer->IsNeedUpdate(NULL, &dirtyRects, &numDirtyRects))
{
IDirect3DTexture9* pTexToUpdate = g_texturesRotation[g_currentTexture];
if (++g_currentTexture == num_textures_in_rotation)
g_currentTexture = 0;
IDirect3DSurface9* pSrcSurface;
hr = pTexToUpdate->GetSurfaceLevel(0, &pSrcSurface);
assert(SUCCEEDED(hr));
HDC surfaceDC;
hr = pSrcSurface->GetDC(&surfaceDC);
assert(SUCCEEDED(hr));
// Draw flash frame
m_flashPlayer->DrawFrame(surfaceDC);
hr = pSrcSurface->ReleaseDC(surfaceDC);
assert(SUCCEEDED(hr));
// Update our GUI texture
IDirect3DSurface9* pDestSurface;
hr = g_Texture->GetSurfaceLevel(0, &pDestSurface);
assert(SUCCEEDED(hr));
for (unsigned int i = 0; i < numDirtyRects; ++i)
{
POINT destPoint = { dirtyRects[i].left, dirtyRects[i].top };
hr = g_pD3DDevice->UpdateSurface(pSrcSurface, dirtyRects + i, pDestSurface, &destPoint);
assert(SUCCEEDED(hr));
}
pDestSurface->Release();
pSrcSurface->Release();
}
// Render our flash file
if (g_Texture)
{
// Generate our new position matrix
currentPos = m_movieArray[playingFlashFile].GetScreenPosition();
Translation.x = currentPos.first;
Translation.y = currentPos.second;
g_Texture->GetLevelDesc(0, &Desc);
Scaling.x = w / (float)m_movieArray[playingFlashFile].GetWidth() /*/ (float)Desc.Width*/;
Scaling.y = h / (float)m_movieArray[playingFlashFile].GetHeight() /*/ (float)Desc.Height*/;
D3DXMatrixTransformation2D(&Mat, NULL, 0, &Scaling, NULL, 0, &Translation);
DWORD oldBlendOP, DestBlend, SrcBlend;
if (screenBlend)
{
// Store old
g_pD3DDevice->GetRenderState(D3DRS_BLENDOP, &oldBlendOP);
g_pD3DDevice->GetRenderState(D3DRS_DESTBLEND, &DestBlend);
g_pD3DDevice->GetRenderState(D3DRS_SRCBLEND, &SrcBlend);
// Set new; photoshop-like Screen filter. Remove the black background
g_pD3DDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
g_pD3DDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCCOLOR);
g_pD3DDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE );
}
// Render sprite
Sprite->Begin(0);
Sprite->SetTransform(&Mat);
Sprite->Draw(g_Texture, NULL, NULL, NULL, 0xFFFFFFFF);
Sprite->End();
// Reset blending
if (screenBlend)
{
g_pD3DDevice->SetRenderState(D3DRS_BLENDOP, oldBlendOP);
g_pD3DDevice->SetRenderState(D3DRS_DESTBLEND, DestBlend);
g_pD3DDevice->SetRenderState(D3DRS_SRCBLEND, SrcBlend);
}
}
}
The rest of the FlashManager functions are easily understandable:
void FlashManager::PlayFlashMovie(int index)
{
if (index >= m_movieArray.size() || index < 0)
{
assert("Bad index");
return;
}
FlashMovie movie = m_movieArray[index];
bool loaded = m_flashPlayer->LoadMovie(StringToWString(movie.GetPath()).c_str());
m_flashPlayer->SetTransparencyMode(transparency_mode);
m_flashPlayer->SetBackgroundColor(RGB(0, 0, 0));
if (!loaded)
{
std::string error = "Could not load flash file: \n";
error += m_movieArray[index].GetPath();
MSGBOX(error.c_str());
assert("Flash load error");
abort();
}
playingFlashFile = index;
lastFlashIndex = index;
}
void FlashManager::SetScreenBlending(bool enableScreen)
{
screenBlend = enableScreen;
}
IDirect3DDevice9* FlashManager::GetD3DDevice()
{
return g_pD3DDevice;
}
FlashManager::~FlashManager()
{
delete m_playerASI;
m_flashDX->DestroyPlayer(m_flashPlayer);
}
bool FlashManager::HasRecreatedTargets()
{
return recreatedTargets;
}
int FlashManager::IsMovieInList(const char* filename)
{
for (int i = 0; i < m_movieArray.size(); i++)
{
if (m_movieArray[i].GetPath().compare(filename) == 0)
return i;
}
return -1;
}
int FlashManager::GetPlayingMovieIndex()
{
return playingFlashFile;
}
int FlashManager::GetLastIndex()
{
return lastFlashIndex;
}
bool FlashManager::IsPlayingMovie()
{
return (playingFlashFile > -1);
}
int FlashManager::AddFlashMovie(FlashMovie fl_movie)
{
m_movieArray.push_back(fl_movie);
return m_movieArray.size()-1;
}
void FlashManager::HookFSCommand(std::string fscommand, void (*func)(const wchar_t* args))
{
m_playerASI->AddFSCCallback(StringToWString(fscommand), func);
}
void FlashManager::ResizeMovie(int index, int Width, int Height)
{
if (index >= m_movieArray.size() || index < 0)
{
assert("Bad index");
return;
}
m_movieArray[index].ResizeMovie(Width, Height, m_flashPlayer);
}
void FlashManager::ChangeFrame(int frame)
{
if (!IsPlayingMovie())
return;
m_flashPlayer->CallFrame(frame);
}
IFlashDXPlayer* FlashManager::GetFlashPlayer()
{
return m_flashPlayer;
}
std::pair<int,int> FlashManager::GetCurrentPos()
{
return currentPos;
}
void FlashManager::SetGameInputProcessing(bool enableInput)
{
enableInputProcessing = enableInput;
}
bool FlashManager::IsGameInputProcessing()
{
return enableInputProcessing;
}
void FlashManager::StopPlayingMovie()
{
m_flashPlayer->StopPlaying();
m_flashPlayer->LoadMovie(L"NULL");
playingFlashFile = -1;
}
D3DXVECTOR2 FlashManager::GetScale()
{
return Scaling;
}
To complete our FlashManager.cpp file, the hacked hooks:
HRESULT APIENTRY d3dSceneHook(LPDIRECT3DDEVICE9 pDevice)
{
if (g_FlashManager)
{
if (g_FlashManager->IsPlayingMovie())
{
if (!g_FlashManager->HasRecreatedTargets())
g_FlashManager->RecreateTargets(pDevice);
g_FlashManager->SceneDataHook();
}
}
return SceneObject(pDevice);
}
// A method to grab the parent HWND from our DLL
BOOL CALLBACK EnumWindowProc(HWND hwnd, LPARAM lParam)
{
HINSTANCE hinst=(HINSTANCE)GetModuleHandle(NULL);
if((HINSTANCE)GetWindowLongPtr(hwnd, GWL_HINSTANCE)==hinst &&
IsWindowVisible(hwnd))
{
hwndMain=hwnd;
return FALSE;
}
else
return TRUE;
}
void InitWinProcHook()
{
hwndMain = NULL;
EnumWindows(EnumWindowProc, 0);
if (hwndMain == NULL)
{
MSGBOX("Failed to find the parent HWND. Tell a programmer");
abort();
}
OldWndProc = (WNDPROC)SetWindowLong(hwndMain, GWL_WNDPROC, (LONG)myWndProcHook);
}
LRESULT CALLBACK myWndProcHook(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (g_FlashManager)
{
IFlashDXPlayer* g_flashPlayer = g_FlashManager->GetFlashPlayer();
bool enableinputs = g_FlashManager->IsGameInputProcessing();
if (!g_FlashManager->IsPlayingMovie())
return CallWindowProc(OldWndProc, hWnd, message, wParam, lParam);
int mousex = GET_X_LPARAM(lParam);
int mousey = GET_Y_LPARAM(lParam);
if (g_FlashManager->IsPlayingMovie())
{
std::pair<int,int> currentPos = g_FlashManager->GetCurrentPos();
mousex -= currentPos.first;
mousey -= currentPos.second;
D3DXVECTOR2 scale = g_FlashManager->GetScale();
mousex /= scale.x;
mousey /= scale.y;
if (mousex < 0) mousex = 0;
if (mousey < 0) mousey = 0;
}
switch (message)
{
case WM_MOUSEMOVE:
if (g_flashPlayer)
g_flashPlayer->SetMousePos(mousex, mousey);
return (enableinputs) ? CallWindowProc(OldWndProc, hWnd, message, wParam, lParam) : 0;
case WM_LBUTTONDOWN:
if (g_flashPlayer)
g_flashPlayer->SetMouseButtonState(mousex, mousey, IFlashDXPlayer::eMouse1, true);
return (enableinputs) ? CallWindowProc(OldWndProc, hWnd, message, wParam, lParam) : 0;
case WM_LBUTTONUP:
if (g_flashPlayer)
g_flashPlayer->SetMouseButtonState(mousex, mousey, IFlashDXPlayer::eMouse1, false);
return (enableinputs) ? CallWindowProc(OldWndProc, hWnd, message, wParam, lParam) : 0;
case WM_KEYDOWN:
if (g_flashPlayer)
g_flashPlayer->SendKey(true, (int)wParam, (int)lParam);
return (enableinputs) ? CallWindowProc(OldWndProc, hWnd, message, wParam, lParam) : 0;
case WM_KEYUP:
if (g_flashPlayer)
g_flashPlayer->SendKey(false, (int)wParam, (int)lParam);
return (enableinputs) ? CallWindowProc(OldWndProc, hWnd, message, wParam, lParam) : 0;
case WM_CHAR:
if (g_flashPlayer)
g_flashPlayer->SendChar((int)wParam, (int)lParam);
return (enableinputs) ? CallWindowProc(OldWndProc, hWnd, message, wParam, lParam) : 0;
case WM_SIZE:
if (g_flashPlayer)
{
}
return (enableinputs) ? CallWindowProc(OldWndProc, hWnd, message, wParam, lParam) : 0;
case WM_SETCURSOR:
if (g_flashPlayer)
{
static bool restoreCursor = true;
if (LOWORD(lParam) != HTCLIENT)
restoreCursor = true;
if (restoreCursor)
{
restoreCursor = false;
break;
}
return (enableinputs) ? CallWindowProc(OldWndProc, hWnd, message, wParam, lParam) : 1;
}
break;
default:
break;
}
}
return CallWindowProc(OldWndProc, hWnd, message, wParam, lParam);
}
Alright, we now have a FlashManager class with hacked access to the D3D device and WndProc. We are nearly done. All we have to do next is initialize our global flashmanager object. Open vgui_int.cpp and add the following under using namespace vgui
:
#include "FlashManager.h"
#include <GameEventListener.h>
Add this under #include "tier0/memdbgon.h"
:
// This is our global flash movie manager
extern FlashManager* g_FlashManager;
In the VGui_Startup function, add this before return true
:
// Init Flash manager
g_FlashManager = new FlashManager();
// Hook some global commands which will be used with our env_flash entity later.
g_FlashManager->HookFSCommand("OnFSCommand1", FSCMD1);
g_FlashManager->HookFSCommand("OnFSCommand2", FSCMD2);
g_FlashManager->HookFSCommand("OnFSCommand3", FSCMD3);
g_FlashManager->HookFSCommand("OnFSCommand4", FSCMD4);
// EXAMPLE title screen hooks. Any time fscommand() is used in ActionScript with the first parameter, the second parameter function is called.
g_FlashManager->HookFSCommand("MSGUI_FASTCMD_QUICKMATCH", FSCMD_QuickMatch);
// Load our Flash Title Screen menu
// First check if the command-line contains a map
std::string cmdline = GetCommandLine();
if (cmdline.find(" map ") == std::string::npos)
{
// I created a flash folder (in the mod folder-- where the models, sound, addons, etc. folders are)
FlashMovie titleScreen("flash\\ui\\title.swf", 1366, 768);
g_FlashManager->PlayFlashMovie(g_FlashManager->AddFlashMovie(titleScreen));
}
Next, delete the pointer when the game is exiting. Add this under vgui::ivgui()->RunFrame();
// Remove flash manager object
delete g_FlashManager;
g_FlashManager = 0;
Add the following functions at the top somewhere:
void FSCMD1(const wchar_t*)
{
IGameEvent* pEvent = gameeventmanager->CreateEvent("fscommand_one");
gameeventmanager->FireEvent(pEvent);
}
void FSCMD2(const wchar_t*)
{
IGameEvent* pEvent = gameeventmanager->CreateEvent("fscommand_two");
gameeventmanager->FireEvent(pEvent);
}
void FSCMD3(const wchar_t*)
{
IGameEvent* pEvent = gameeventmanager->CreateEvent("fscommand_three");
gameeventmanager->FireEvent(pEvent);
}
void FSCMD4(const wchar_t*)
{
IGameEvent* pEvent = gameeventmanager->CreateEvent("fscommand_four");
gameeventmanager->FireEvent(pEvent);
}
// EXAMPLE function
void FSCMD_QuickMatch(const wchar_t*)
{
// Stop our title screen from playing
g_FlashManager->StopPlayingMovie();
// Now load our example map
engine->ExecuteClientCmd("map flash_movie_example");
}
Now, everything in vgui_int.cpp is the actual usage of our FlashManager class. You interact between your Flash file and the FlashManager through ActionScript FSCommands. You can link fscommands to c++ functions using g_FlashManager->HookFSCommand(...)
. The four "OnFSCommand" stuff will actually be used by our server entity class next.
The Server-side Code
We're almost done with the implementation. Create a new file in your Server SDK project called env_flash.cpp and add this:
#include "cbase.h"
#include <GameEventListener.h>
class CFlashEnvironment : public CLogicalEntity, public CGameEventListener
{
private:
COutputEvent m_FSOne, m_FSTwo, m_FSThree, m_FSFour;
char* m_szFlashName;
bool m_IsEnabled, enableScreenBlend;
int m_xPos, m_yPos, m_Width, m_Height;
public:
DECLARE_CLASS( CFlashEnvironment, CLogicalEntity );
DECLARE_DATADESC();
// Constructor
CFlashEnvironment()
{
// make sure events are loaded
gameeventmanager->LoadEventsFromFile("resource/modevents.res");
// set defaults
m_szFlashName = "env_flash filename not set";
m_IsEnabled = false;
m_xPos = 0;
m_yPos = 0;
m_Width = -1;
m_Height = -1;
// build our FSCommand table
ListenForGameEvent("fscommand_one");
ListenForGameEvent("fscommand_two");
ListenForGameEvent("fscommand_three");
ListenForGameEvent("fscommand_four");
}
void FireGameEvent(IGameEvent* pEvent)
{
if (!strcmp("fscommand_one", pEvent->GetName()))
FSCommand1();
else if (!strcmp("fscommand_two", pEvent->GetName()))
FSCommand2();
else if (!strcmp("fscommand_three", pEvent->GetName()))
FSCommand3();
else if (!strcmp("fscommand_four", pEvent->GetName()))
FSCommand4();
}
// Output functions
void FSCommand1();
void FSCommand2();
void FSCommand3();
void FSCommand4();
// Input functions
void RenderFlashMovie( inputdata_t &inputData );
void StopMovie( inputdata_t &inputData );
void StartMovie( inputdata_t &inputData );
};
LINK_ENTITY_TO_CLASS( env_flash, CFlashEnvironment );
BEGIN_DATADESC( CFlashEnvironment )
// Stores the name of the flash movie to play
DEFINE_KEYFIELD( m_szFlashName, FIELD_STRING, "file" ),
// Storage of whether or not our flash movie is "playing"
DEFINE_FIELD( m_IsEnabled, FIELD_BOOLEAN ),
// X and Y position on the screen
DEFINE_KEYFIELD( m_xPos, FIELD_INTEGER, "x" ),
DEFINE_KEYFIELD( m_yPos, FIELD_INTEGER, "y" ),
// Width and Height
DEFINE_KEYFIELD( m_Width, FIELD_INTEGER, "width" ),
DEFINE_KEYFIELD( m_Height, FIELD_INTEGER, "height" ),
// Blending
DEFINE_KEYFIELD( enableScreenBlend, FIELD_BOOLEAN, "blend" ),
// Links our input for rendering event
DEFINE_INPUTFUNC( FIELD_VOID, "RenderFlashMovie", RenderFlashMovie ),
DEFINE_INPUTFUNC( FIELD_VOID, "StopMovie", StopMovie ),
DEFINE_INPUTFUNC( FIELD_VOID, "StartMovie", StartMovie ),
// Links our output member to the output name used by Hammer
DEFINE_OUTPUT( m_FSOne, "OnFSCommand1" ),
DEFINE_OUTPUT( m_FSTwo, "OnFSCommand2" ),
DEFINE_OUTPUT( m_FSThree, "OnFSCommand3" ),
DEFINE_OUTPUT( m_FSFour, "OnFSCommand4" ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose: Fire a network event to change rendering HUD
//-----------------------------------------------------------------------------
void CFlashEnvironment::RenderFlashMovie( inputdata_t &inputData )
{
Msg("Starting render_flash_hud event with flash file %s", m_szFlashName);
IGameEvent* pEvent = gameeventmanager->CreateEvent("render_flash_hud");
pEvent->SetString("file", m_szFlashName);
pEvent->SetInt("x", m_xPos);
pEvent->SetInt("y", m_yPos);
pEvent->SetInt("width", m_Width);
pEvent->SetInt("height", m_Height);
pEvent->SetBool("blend", enableScreenBlend);
gameeventmanager->FireEvent(pEvent);
}
void CFlashEnvironment::StopMovie( inputdata_t &inputData )
{
m_IsEnabled = false;
IGameEvent* pEvent = gameeventmanager->CreateEvent("flash_hud_toggle");
pEvent->SetBool("enable", m_IsEnabled);
gameeventmanager->FireEvent(pEvent);
}
void CFlashEnvironment::StartMovie( inputdata_t &inputData )
{
m_IsEnabled = true;
IGameEvent* pEvent = gameeventmanager->CreateEvent("flash_hud_toggle");
pEvent->SetBool("enable", m_IsEnabled);
gameeventmanager->FireEvent(pEvent);
}
//-----------------------------------------------------------------------------
// Purpose: Fire Hammer events based on FSCommands
//-----------------------------------------------------------------------------
void CFlashEnvironment::FSCommand1()
{
m_FSOne.FireOutput(this, this);
}
void CFlashEnvironment::FSCommand2()
{
m_FSTwo.FireOutput(this, this);
}
void CFlashEnvironment::FSCommand3()
{
m_FSThree.FireOutput(this, this);
}
void CFlashEnvironment::FSCommand4()
{
m_FSFour.FireOutput(this, this);
}
The env_flash entity is a simple logical entity that hooks fscommand('OnFSCommandX')
where X is a number between 1-4. It fires linked outputs when the command is sent from the flash file. This is useful to the mapper for a variety of things: e.g. having the user play a minigame and sending OnFSCommand1 for victory or OnFSCommand2 for a loss and firing entities subsequently that decide the fate of the character is one example of its usage.
Anyway, in order for the entity to be accessible, you need to add the following to your mod's FGD file:
@PointClass base(Targetname) = env_flash : "Manages flash UI."
[
file(string) : "File Name" : "" : "Path to flash file (e.g. flash\ui\title.swf)"
x(integer) : "X Position" : 0 : "Distance from the left of the screen"
y(integer) : "Y Position" : 0 : "Distance from the top of the screen"
width(integer) : "Width" : -1 : "Width. Use -1 to auto-detect"
height(integer) : "Height" : -1 : "Height. Use -1 to auto-detect"
blend(integer) : "Blending" : 1 : "Enables alpha blending (similar to photoshop's Screen blend mode), where black colors are made transparent"
// Inputs
input RenderFlashMovie(void) : "Loads and renders the SWF flash movie"
input StopMovie(void) : "Stops any playing SWF flash movie"
input StartMovie(void) : "Stops any playing SWF flash movie"
// Outputs
output OnFSCommand1(void) : "Called whenever the Flash ActionScript uses fscommand('OnFSCommand1');"
output OnFSCommand2(void) : "Called whenever the Flash ActionScript uses fscommand('OnFSCommand2');"
output OnFSCommand3(void) : "Called whenever the Flash ActionScript uses fscommand('OnFSCommand3');"
output OnFSCommand4(void) : "Called whenever the Flash ActionScript uses fscommand('OnFSCommand4');"
]
And add the following events to your mod's modevents.res:
"render_flash_hud"
{
"file" "string" // name of flash file to render
"x" "short" // x position of file to render
"y" "short" // y position of file to render
"width" "short" // width to render the SWF in
"height" "short" // height to render the SWF in
"blend" "bool" // Enable screen alpha blending
}
"flash_hud_toggle"
{
"enable" "bool" // True to enable the flash HUD, false to disable
}
"fscommand_one"
{
}
"fscommand_two"
{
}
"fscommand_three"
{
}
"fscommand_four"
{
}
Conclusion
You have completed integrating Adobe Flash into your mod! At this point in time, you should be able to place a flash file in /sourcemods/yourmod/flash/ui/ called title.swf and have it display when you start up the game. You should also have access to the env_flash entity in Hammer (don't forget to add your mod's FGD to hammer options). Lastly, you may want to remove the old title screen by editing GameMenu.res to the following:
"GameMenu"
{
}