Implementing Adobe Flash
January 2024
Introduction
Adobe Flash is a powerful and versatile tool for designing user interfaces. Many modern AAA games utilize Adobe Flash for the UI; notable games include Crysis 2, Dead Space 2, Mass Effect 2, and Counter-Strike: Global Offensive. This tutorial will teach you how to set up your own Adobe Flash interface for use with the Source SDK. This method will require a bit of hacking, however.
Please keep in mind that the interface we will implement is not optimized for gaming. Using the same hack, however, one can just as easily implement a Web UI bridge like Ultralight (formerly Awesomium). Also keep in mind that the code we will write here does not follow game industry standards; if this is for a professional mod, you can have fun fixing my rushed code.
You can see a live demo of the following implementation here: Flash Implementation Video
Prerequisites
You will need to grab a copy of FlashDX. Flash-To-DirectX will really speed up the process of integrating Flash user interfaces. Keep in mind that FlashDX is only wrapping the COM component of Flash; the component is not optimized for gaming and will notably reduce your frame rate when using complicated flash files. If speed and compatibility is important for you, consider following the hack but implement Scaleform or something similar in FlashDX's place.
Next, download the June 2010 edition of the DirectX SDK. Newer versions are missing some stuff, so to save you the pain of debugging, install the June 2010 edition.
Getting Started
I've heavily modified my copy of Flash DX; I got rid of the transparency mechanism and replaced it with a custom written color keying algorithm, wrote a method for rendering Flash objects onto meshes, and wrote a method to create a projected flash matrix. However, for this tutorial's purpose, the unmodified FlashDX will work fine. Compile FlashDX into a Multi-threaded (/MT) static library (in Release mode). If you don't change the threading type, you're going to have a lot of weird linker errors.
Next, switch over to your Source SDK mod. Go to Project > Client SDK Properties (I am using Visual Studio 2012; works the same way in VS 2010-- just right-click on Client SDK project filter and select Properties). In the tree, select the "VC++ Directories" node. Add the FlashDX and DirectX SDK libraries and includes to your VC++ Directories. I shouldn't have to explain every little process for this.
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"
{
}