summaryrefslogtreecommitdiff
path: root/game/client/econ/tool_items
diff options
context:
space:
mode:
Diffstat (limited to 'game/client/econ/tool_items')
-rw-r--r--game/client/econ/tool_items/custom_texture_cache.cpp1111
-rw-r--r--game/client/econ/tool_items/custom_texture_cache.h83
-rw-r--r--game/client/econ/tool_items/custom_texture_tool.cpp2766
-rw-r--r--game/client/econ/tool_items/decoder_ring_tool.cpp213
-rw-r--r--game/client/econ/tool_items/decoder_ring_tool.h14
-rw-r--r--game/client/econ/tool_items/gift_wrap_tool.cpp241
-rw-r--r--game/client/econ/tool_items/gift_wrap_tool.h14
-rw-r--r--game/client/econ/tool_items/paint_can_tool.cpp205
-rw-r--r--game/client/econ/tool_items/paint_can_tool.h15
-rw-r--r--game/client/econ/tool_items/rename_tool_ui.cpp306
-rw-r--r--game/client/econ/tool_items/rename_tool_ui.h74
-rw-r--r--game/client/econ/tool_items/tool_items.cpp968
-rw-r--r--game/client/econ/tool_items/tool_items.h58
13 files changed, 6068 insertions, 0 deletions
diff --git a/game/client/econ/tool_items/custom_texture_cache.cpp b/game/client/econ/tool_items/custom_texture_cache.cpp
new file mode 100644
index 0000000..12cdd34
--- /dev/null
+++ b/game/client/econ/tool_items/custom_texture_cache.cpp
@@ -0,0 +1,1111 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+#include "cbase.h"
+#include "custom_texture_cache.h"
+#include "materialsystem/imaterialproxy.h"
+#include "materialsystem/imaterialvar.h"
+#include "materialsystem/itexture.h"
+#include "pixelwriter.h"
+#include "checksum_md5.h"
+#include "imageutils.h"
+#include "toolframework_client.h"
+#include "econ_gcmessages.h"
+#include "econ_item_inventory.h"
+
+#include "VGuiMatSurface/IMatSystemSurface.h"
+#include "bitmap/bitmap.h"
+
+using namespace CustomTextureSystem;
+
+ITexture *CustomTextureSystem::g_pPreviewCustomTexture = NULL;
+
+CEconItemView *CustomTextureSystem::g_pPreviewEconItem = NULL;
+
+bool CustomTextureSystem::g_pPreviewCustomTextureDirty = true;
+
+const char CustomTextureSystem::k_rchCustomTextureFilterPreviewImageName[] = "__CustomTextureFilterPreview";
+const char CustomTextureSystem::k_rchCustomTextureFilterPreviewTextureName[] = "vgui/__CustomTextureFilterPreview";
+
+//-----------------------------------------------------------------------------
+
+static ISteamRemoteStorage *GetISteamRemoteStorage()
+{
+ return steamapicontext?steamapicontext->SteamRemoteStorage():NULL;
+// return Steam3Client().SteamRemoteStorage();
+}
+
+static void CalcMD5Ascii( char *szDigestAscii, const void *data, int dataSz )
+{
+ MD5Context_t context;
+ unsigned char digest[ MD5_DIGEST_LENGTH ];
+ MD5Init( &context );
+ MD5Update( &context, (const unsigned char *)data, dataSz );
+ MD5Final( digest, &context );
+ Q_binarytohex( digest, MD5_DIGEST_LENGTH, szDigestAscii, MD5_DIGEST_LENGTH*2+1 );
+}
+
+static bool BReadSteamRemoteFileToBuffer( CUtlBuffer &outBuffer, const char *pchRemoteFilename )
+{
+ outBuffer.Purge();
+
+ ISteamRemoteStorage *pRemoteStorage = GetISteamRemoteStorage();
+ if ( !pRemoteStorage )
+ return false;
+ if ( !pRemoteStorage->FileExists( pchRemoteFilename ))
+ return false;
+ int nFileSize = pRemoteStorage->GetFileSize( pchRemoteFilename );
+ if ( nFileSize <= 0 )
+ return false;
+
+ // Allocate space
+ outBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, nFileSize );
+ int nSizeRead = pRemoteStorage->FileRead( pchRemoteFilename, outBuffer.Base(), nFileSize );
+ return ( nSizeRead == nFileSize );
+}
+
+//-----------------------------------------------------------------------------
+// Local cache of custom images. This cache contains files from the cloud, and
+// and also a few virtual textures that are used during autioning / tweaking
+
+/// Name of cloud-backed config file remembering the custom images that the user
+/// has uploaded to the cloud.
+static const char k_szCustomTextureRecentListFilename[] = "stamped_items_mru.txt";
+
+/// Track a single entry
+struct SCustomImageCacheEntry : private ITextureRegenerator
+{
+
+ /// If this has been assigned a cloud ID, what is it?
+ UGCHandle_t m_hCloudID;
+
+ /// If this is one of our files, then we know the MD5.
+ /// This is empty for other people's files
+ char m_szDigestAscii[ MD5_DIGEST_LENGTH*2 + 4];
+
+ //
+ // Bookkeeping for steam downloads of UGC.
+ //
+
+ /// -1 = failure, 0 = not started, 1 = in progress, 2 = finished OK and image should be in memory
+ int m_nStatus;
+
+ /// Handle to the active download, or k_uAPICallInvalid if not active
+ SteamAPICall_t m_hDownloadApiCall;
+
+ /// Procedural texture object. We hold a reference.
+ ITexture *m_pTexture;
+
+ /// Procedurally-created material. We hold a reference.
+ IMaterial *m_pMaterial;
+
+ /// GUI texture handle. (It's bound to the material.)
+ int m_iVguiHandle;
+
+ /// The raw image
+ Bitmap_t m_image;
+
+ /// Doubly-linked list. We keep it in MRU order so we know what to eject from the cache
+ SCustomImageCacheEntry *m_pPrev;
+ SCustomImageCacheEntry *m_pNext;
+
+ SCustomImageCacheEntry()
+ : m_hCloudID(0)
+ , m_pTexture(NULL)
+ , m_nStatus(0)
+ , m_hDownloadApiCall(k_uAPICallInvalid)
+ , m_pPrev(NULL)
+ , m_pNext(NULL)
+ , m_pMaterial(NULL)
+ , m_iVguiHandle(0)
+ {
+ m_szDigestAscii[0] = '\0';
+ }
+
+ virtual ~SCustomImageCacheEntry()
+ {
+ Clear();
+ }
+
+ // Release texture / VGUI resources. This doesn't free the image we have
+ // loaded or stop any async actions that were in progress. (Use Clear())
+ void ReleaseResources()
+ {
+ if ( m_pTexture )
+ {
+ ITexture *tex = m_pTexture;
+ m_pTexture = NULL; // clear pointer first, to prevent infinite recursion
+ tex->SetTextureRegenerator( NULL );
+ tex->Release();
+ }
+ if ( m_pMaterial )
+ {
+ m_pMaterial->Release();
+ m_pMaterial = NULL;
+ }
+ if ( m_iVguiHandle != 0 )
+ {
+ g_pMatSystemSurface->DestroyTextureID( m_iVguiHandle );
+ m_iVguiHandle = 0;
+ }
+ }
+
+ /// Inherited from ITextureRegenerator
+ ///
+ /// Gets called when our ITextureRegenerator interface gets detached from the texture.
+ /// We should be the only ones doing this --- so that means we had better have already
+ /// cleared our texture at this point!
+ virtual void Release()
+ {
+ Assert( m_pTexture == NULL );
+ }
+
+ /// Inherited from ITextureRegenerator
+ ///
+ /// The main interface function that actually supplies the texture bits
+ virtual void RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect )
+ {
+
+ Assert( pVTFTexture->FrameCount() == 1 );
+ Assert( pVTFTexture->FaceCount() == 1 );
+ Assert( pTexture == m_pTexture );
+ Assert( !pTexture->IsMipmapped() );
+
+ int nWidth, nHeight, nDepth;
+ pVTFTexture->ComputeMipLevelDimensions( 0, &nWidth, &nHeight, &nDepth );
+ Assert( nDepth == 1 );
+ Assert( nWidth == m_image.Width() && nHeight == m_image.Height() );
+
+ CPixelWriter pixelWriter;
+ pixelWriter.SetPixelMemory( pVTFTexture->Format(),
+ pVTFTexture->ImageData( 0, 0, 0 ), pVTFTexture->RowSizeInBytes( 0 ) );
+
+ // !SPEED! 'Tis probably DEATHLY slow...
+ for ( int y = 0; y < nHeight; ++y )
+ {
+ pixelWriter.Seek( 0, y );
+ for ( int x = 0; x < nWidth; ++x )
+ {
+ Color c = m_image.GetColor( x, y );
+ pixelWriter.WritePixel( c.r(), c.g(), c.b(), c.a() );
+ }
+ }
+ }
+
+ void Clear()
+ {
+ ReleaseResources();
+ m_image.Clear();
+ m_szDigestAscii[0] = '\0';
+ m_nStatus = 0;
+ m_hCloudID = 0;
+
+ // !KLUDGE! How can I clean this up properly if something is
+ // in progress?
+ m_hDownloadApiCall = k_uAPICallInvalid;
+ }
+
+ /// Poll the entry and update bookeeping if we're busy.
+ void Poll()
+ {
+
+ // We must know our cloud ID
+ if ( m_hCloudID == 0 )
+ {
+ Assert( m_hCloudID != 0 );
+ return;
+ }
+
+ // If texture already exists, then we are definitely done!
+ if ( m_pTexture )
+ {
+ Assert( m_nStatus == 2 );
+ return;
+ }
+
+ // Check if we have not yet initiated anything
+ if ( m_nStatus == 0 )
+ {
+
+ // We'll need to download it.
+ // Start by assuming failure.
+ m_nStatus = -1;
+
+ // Start download
+ ISteamRemoteStorage *pRemoteStorage = GetISteamRemoteStorage();
+ if ( pRemoteStorage )
+ {
+ m_hDownloadApiCall = pRemoteStorage->UGCDownload( m_hCloudID, 0 );
+ if ( m_hDownloadApiCall != k_uAPICallInvalid )
+ {
+ // Mark download as in progress
+ Msg( "Started download of cloud file %08X%08X\n", (uint32)(m_hCloudID>>32), (uint32)m_hCloudID );
+ m_nStatus = 1;
+ }
+ }
+ }
+
+ // If we're in progress, poll the result
+ if ( m_nStatus == 1 )
+ {
+ PollDownload();
+ }
+
+ // If result has completed, then fetch the texture
+ Assert( m_pTexture == NULL );
+ if ( m_nStatus == 2 && m_image.IsValid() )
+ {
+ // Generate the logical texture name
+ char rchTextureName[MAX_PATH];
+ GenerateLocalTextureName( rchTextureName );
+
+ ITexture *pTexture = NULL;
+ if ( g_pMaterialSystem->IsTextureLoaded( rchTextureName ) )
+ {
+ pTexture = g_pMaterialSystem->FindTexture( rchTextureName, TEXTURE_GROUP_VGUI );
+ pTexture->AddRef();
+ Assert( pTexture );
+ }
+ else
+ {
+ pTexture = g_pMaterialSystem->CreateProceduralTexture(
+ rchTextureName,
+ TEXTURE_GROUP_VGUI,
+ k_nCustomImageSize, k_nCustomImageSize,
+ IMAGE_FORMAT_RGBA8888,
+ TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT | TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_NOLOD
+ );
+ Assert( pTexture );
+ }
+ pTexture->SetTextureRegenerator( this ); // note carefully order of operations here. See Release()
+ m_pTexture = pTexture;
+
+ // Upload the data now
+ m_pTexture->Download();
+ }
+ else
+ {
+ Assert( m_nStatus < 2 );
+ Assert( !m_image.IsValid() );
+ }
+ }
+
+ void PollDownload()
+ {
+
+ Assert( m_nStatus == 1 );
+
+ // Sanity check we have everything we need
+ ISteamRemoteStorage *pRemoteStorage = GetISteamRemoteStorage();
+ if ( m_hDownloadApiCall == k_uAPICallInvalid || !steamapicontext || !steamapicontext->SteamUtils() || !pRemoteStorage )
+ {
+ // ???
+ Assert( m_hDownloadApiCall != k_uAPICallInvalid );
+ Assert( steamapicontext && steamapicontext->SteamUtils() );
+ Assert( pRemoteStorage );
+ m_nStatus = -1;
+ return;
+ }
+
+ // Poll progress
+ bool bFailed;
+ RemoteStorageDownloadUGCResult_t result;
+ if ( !steamapicontext->SteamUtils()->GetAPICallResult(m_hDownloadApiCall,
+ &result, sizeof(result), RemoteStorageDownloadUGCResult_t::k_iCallback, &bFailed) )
+ {
+ // Still busy.
+ return;
+ }
+
+ // Make sure we got back the file we were expecting
+ Assert( result.m_hFile == m_hCloudID );
+
+ // Clear status, mark success
+ m_hDownloadApiCall = k_uAPICallInvalid;
+
+ // Completed. Did we succeed?
+ if ( bFailed )
+ {
+ Warning( "Download of custom image file from UFS (UGC=%08X%08X) failed.\n", (uint32)(m_hCloudID >> 32), (uint32)(m_hCloudID) );
+ m_nStatus = -1;
+ return;
+ }
+
+ // Fetch file details
+ AppId_t nAppID;
+ char *pchName;
+ int32 nFileSizeInBytes = -1;
+ CSteamID steamIDOwner;
+ if ( !pRemoteStorage->GetUGCDetails( m_hCloudID, &nAppID, &pchName, &nFileSizeInBytes, &steamIDOwner )
+ || nFileSizeInBytes <= 0 || nFileSizeInBytes >= k_nMaxCustomImageFileSize )
+ {
+ Warning( "GetUGCDetails failed? (UGC=%08X%08X nFileSizeInBytes=%d).\n", (uint32)(m_hCloudID >> 32), (uint32)(m_hCloudID), nFileSizeInBytes );
+ m_nStatus = -1;
+ return;
+ }
+
+ // Load the file data
+ CUtlBuffer fileData;
+ fileData.SeekPut( CUtlBuffer::SEEK_HEAD, nFileSizeInBytes );
+
+ // Read in the data. Phil says this is supposed to be basically a memcpy
+ // or some other fast, local operation.
+ if ( pRemoteStorage->UGCRead( m_hCloudID, fileData.Base( ), nFileSizeInBytes, 0, k_EUGCRead_ContinueReadingUntilFinished ) != nFileSizeInBytes )
+ {
+ Warning( "UGCRead failed? (UGC=%08X%08X).\n", (uint32)(m_hCloudID >> 32), (uint32)(m_hCloudID) );
+ m_nStatus = -1;
+ return;
+ }
+
+ // Parse the PNG file data
+ if ( ImgUtl_LoadPNGBitmapFromBuffer( fileData, m_image ) != CE_SUCCESS )
+ {
+ Warning( "Corrupt PNG file, UGC=%08X%08X.\n", (uint32)(m_hCloudID >> 32), (uint32)(m_hCloudID) );
+ m_nStatus = -1;
+ return;
+ }
+
+ // We have the raw data
+ m_nStatus = 2;
+ }
+
+ int GetGuiHandle()
+ {
+ // Should never be called on entries without a cloud ID
+ if ( m_hCloudID == 0 )
+ {
+ Assert( m_hCloudID != 0 );
+ return 0;
+ }
+
+ // Already have one?
+ if ( m_iVguiHandle != 0 )
+ {
+ return m_iVguiHandle;
+ }
+
+ // Process texture downloading, etc
+ Poll();
+
+ // If we don't have a texture yet, or don't know our logical name, then we cannot draw
+ if ( m_pTexture == NULL )
+ {
+ return 0;
+ }
+
+ // Make a material, if we don't already have one
+ if ( m_pMaterial == NULL )
+ {
+
+ // Generate the material name
+ char rchImageName[MAX_PATH], rchMaterialName[MAX_PATH];
+ GenerateLocalImageNameBase( rchImageName );
+ Q_snprintf( rchMaterialName, MAX_PATH, "vgui/%s.mtl", rchImageName );
+
+ // Does it already exist?
+ if ( g_pMaterialSystem->IsMaterialLoaded( rchMaterialName ) )
+ {
+ m_pMaterial = g_pMaterialSystem->FindMaterial( rchMaterialName, TEXTURE_GROUP_VGUI );
+ Assert( m_pMaterial );
+ }
+ else
+ {
+
+ // Fetch the texture name
+ char rchTextureName[MAX_PATH];
+ GenerateLocalTextureName( rchTextureName );
+
+ // Create dummy material KV data
+ KeyValues *pVMTKeyValues = new KeyValues( "UnlitGeneric" );
+ pVMTKeyValues->SetString( "$basetexture", rchTextureName );
+ pVMTKeyValues->SetInt( "$vertexcolor", 1 );
+ pVMTKeyValues->SetInt( "$vertexalpha", 1 );
+ pVMTKeyValues->SetInt( "$translucent", 1 );
+
+ // Create the material
+ m_pMaterial = g_pMaterialSystem->CreateMaterial(
+ rchMaterialName,
+ pVMTKeyValues
+ );
+ }
+
+ // Bind the material to a new VGUI texture object
+ m_iVguiHandle = g_pMatSystemSurface->CreateNewTextureID();
+ g_pMatSystemSurface->DrawSetTextureMaterial( m_iVguiHandle, m_pMaterial );
+ }
+
+ return m_iVguiHandle;
+ }
+
+ // Generate logical image name, with no leading materials or vgui directories
+ // nor a file extension.
+ void GenerateLocalImageNameBase( char *result ) const
+ {
+ Assert( m_hCloudID != 0 );
+
+ // Generate the local filenames. !KLUDGE! I'm not sure the platform-safe way
+ // to print a 64-bit int, so I'll just print both halves myself
+ Q_snprintf( result, 64, "cloud_custom_images/%08X%08X", (uint32)(m_hCloudID >> 32), (uint32)(m_hCloudID) );
+ }
+
+ /// Logical texture name, including "vgui" but not "materials"
+ void GenerateLocalTextureName( char *result ) const
+ {
+ char rchImageName[MAX_PATH];
+ GenerateLocalImageNameBase( rchImageName );
+ Q_snprintf( result, MAX_PATH, "vgui/%s.vtf", rchImageName );
+ }
+
+ /// Full local filename, including leading "materials" directory
+ void GenerateLocalFilename( char *result ) const
+ {
+ char szLocalTextureName[MAX_PATH];
+ GenerateLocalTextureName( szLocalTextureName );
+
+ Q_snprintf( result, MAX_PATH, "materials/%s", szLocalTextureName );
+ }
+};
+
+/// Head of linked list of entries, in MRU order
+static SCustomImageCacheEntry *mruCustomImageEntry = NULL;
+
+/// Map of entries, indexed by cloud ID
+typedef CUtlMap<UGCHandle_t, SCustomImageCacheEntry *, int> tCustomTextureInfoMap;
+static tCustomTextureInfoMap g_mapCustomTextureInfoByCloudId( DefLessFunc(UGCHandle_t) );
+
+// Remove from linked list, without deleting. The item must already be in the list
+static void CustomTextureCache_Remove(SCustomImageCacheEntry *pEntry)
+{
+ Assert( pEntry );
+
+ // List had better not be empty. Commence paranoia.
+ Assert( mruCustomImageEntry );
+ Assert( !mruCustomImageEntry->m_pPrev );
+
+ SCustomImageCacheEntry *p = pEntry->m_pPrev;
+ SCustomImageCacheEntry *n = pEntry->m_pNext;
+
+ // Detach from next, if we're not last
+ if ( n != NULL )
+ {
+ Assert( n->m_pPrev == pEntry);
+ n->m_pPrev = p;
+ }
+
+ // At the head?
+ if ( !p )
+ {
+ Assert( mruCustomImageEntry == pEntry );
+ mruCustomImageEntry = n;
+ }
+ else
+ {
+
+ // Detach from previous
+ Assert( p->m_pNext == pEntry );
+ p->m_pNext = n;
+ }
+
+ // Clear pointers
+ pEntry->m_pPrev = pEntry->m_pNext = NULL;
+}
+
+// Insert the item at the head (MRU) slot. The item shouldn't
+// already be in the list
+static void CustomTextureCache_InsertAtHead(SCustomImageCacheEntry *pEntry)
+{
+ Assert( pEntry );
+ Assert( !pEntry->m_pNext );
+ Assert( !pEntry->m_pPrev );
+
+ // Edge case of inserting into empty list
+ if ( mruCustomImageEntry )
+ {
+ Assert( !mruCustomImageEntry->m_pPrev );
+ mruCustomImageEntry->m_pPrev = pEntry;
+ }
+ else
+ {
+ // Inserting into an empty list
+ }
+
+ // Do the head insertion.
+ pEntry->m_pNext = mruCustomImageEntry;
+ mruCustomImageEntry = pEntry;
+}
+
+// Reorder list, setting item at the head (MRU) slot. The item must already
+// be in the list somewhere.
+static void CustomTextureCache_SetMRU(SCustomImageCacheEntry *pEntry)
+{
+ // Note: even if we are already at the head, go through the motions, anyway,
+ // to exercise all of the sanity checking code.
+ CustomTextureCache_Remove(pEntry);
+ CustomTextureCache_InsertAtHead(pEntry);
+}
+
+static SCustomImageCacheEntry *CustomTextureCache_NewEntry()
+{
+
+ SCustomImageCacheEntry *pEntry = new SCustomImageCacheEntry;
+
+ // Go ahead and put us at the head
+ CustomTextureCache_InsertAtHead(pEntry);
+
+ // Return the new entry
+ return pEntry;
+}
+
+static SCustomImageCacheEntry *CustomTextureCache_FindOrAddByCloudId( UGCHandle_t ugcHandle )
+{
+
+ // Locate the bookeeping entry, if one exists
+ int idx = g_mapCustomTextureInfoByCloudId.Find( ugcHandle );
+ SCustomImageCacheEntry *pEntry;
+ if ( g_mapCustomTextureInfoByCloudId.IsValidIndex( idx ) )
+ {
+ pEntry = g_mapCustomTextureInfoByCloudId[idx];
+
+ // We're accessing it, so move it to the head, the MRU slot
+ CustomTextureCache_SetMRU(pEntry);
+ }
+ else
+ {
+ // Grab a new entry
+ pEntry = CustomTextureCache_NewEntry();
+
+ // Assign the cloud ID
+ pEntry->m_hCloudID = ugcHandle;
+
+ // Add it to the map by cloud ID
+ idx = g_mapCustomTextureInfoByCloudId.Insert( ugcHandle );
+ g_mapCustomTextureInfoByCloudId[idx] = pEntry;
+ }
+
+ // Return the entry
+ return pEntry;
+}
+
+// Locate an entry by hash create a new entry if one doesn't already exist
+static SCustomImageCacheEntry *CustomTextureCache_FindOrAddByDigest( const char *szDigestAscii )
+{
+ Assert( strlen(szDigestAscii) == MD5_DIGEST_LENGTH*2 );
+
+ // Brute-force linear search. This should never be called in time-critical
+ // situations
+ SCustomImageCacheEntry *pEntry = mruCustomImageEntry;
+ while ( pEntry )
+ {
+
+ // Match?
+ if ( !Q_stricmp(pEntry->m_szDigestAscii, szDigestAscii) )
+ {
+
+ // Found. Se at MRU and return it.
+ CustomTextureCache_SetMRU(pEntry);
+ return pEntry;
+ }
+
+ // Keep looking
+ pEntry = pEntry->m_pNext;
+ }
+
+ // Not found. Make a new entry
+ pEntry = CustomTextureCache_NewEntry();
+ V_strcpy_safe(pEntry->m_szDigestAscii, szDigestAscii);
+ return pEntry;
+}
+
+//-----------------------------------------------------------------------------
+int GetCustomTextureGuiHandle( uint64 hCloudId )
+{
+
+ // Find or create the entry
+ SCustomImageCacheEntry *pEntry = CustomTextureCache_FindOrAddByCloudId( hCloudId );
+
+ // Poll entry and return GUI handle if it's finally ready
+ return pEntry->GetGuiHandle();
+}
+
+//-----------------------------------------------------------------------------
+
+class CCustomTextureOnItemProxy : public IMaterialProxy
+{
+public:
+ CCustomTextureOnItemProxy();
+ virtual ~CCustomTextureOnItemProxy();
+
+ virtual bool Init( IMaterial* pMaterial, KeyValues *pKeyValues );
+ virtual void OnBind( void *pC_BaseEntity );
+ virtual void Release();
+ virtual IMaterial *GetMaterial();
+
+protected:
+ virtual void OnBindInternal( CEconItemView *pScriptItem );
+
+private:
+ IMaterialVar *m_pBaseTextureVar;
+ ITexture *m_pOriginalTexture;
+};
+
+EXPOSE_INTERFACE( CCustomTextureOnItemProxy, IMaterialProxy, "CustomSteamImageOnModel" IMATERIAL_PROXY_INTERFACE_VERSION );
+
+CCustomTextureOnItemProxy::CCustomTextureOnItemProxy()
+: m_pBaseTextureVar( NULL )
+, m_pOriginalTexture( NULL )
+{
+
+}
+
+CCustomTextureOnItemProxy::~CCustomTextureOnItemProxy()
+{
+}
+
+bool CCustomTextureOnItemProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues )
+{
+ Release();
+
+ bool found = false;
+ m_pBaseTextureVar = pMaterial->FindVar( "$basetexture", &found );
+ if ( !found )
+ {
+ return false;
+ }
+
+ // No! Don't do this until, because the material/texture might
+ // not have been cached. If we call this, it causes the material
+ // to try to get cached, but instead of loading the texture
+ // synchronously, it just goes into a queue, and we get the error
+ // texture instead. We'll just defer it until later when we know
+ // for sure that everything is ready to go.
+ //m_pOriginalTexture = m_pBaseTextureVar->GetTextureValue();
+ //if ( m_pOriginalTexture )
+ //{
+ // m_pOriginalTexture->AddRef();
+ //}
+ return true;
+}
+
+void CCustomTextureOnItemProxy::OnBind( void *pC_BaseEntity )
+{
+ if ( pC_BaseEntity )
+ {
+ CEconItemView *pScriptItem = NULL;
+ IClientRenderable *pRend = (IClientRenderable *)pC_BaseEntity;
+ C_BaseEntity *pEntity = pRend->GetIClientUnknown()->GetBaseEntity();
+ if ( pEntity )
+ {
+ CEconEntity *pItem = dynamic_cast< CEconEntity* >( pEntity );
+ if ( pItem )
+ {
+ pScriptItem = pItem->GetAttributeContainer()->GetItem();
+ }
+ }
+ else
+ {
+ // Proxy data can be a script created item itself, if we're in a vgui CModelPanel
+ pScriptItem = dynamic_cast< CEconItemView* >( pRend );
+ }
+ if ( pScriptItem )
+ {
+ OnBindInternal( pScriptItem );
+ }
+ }
+}
+
+void CCustomTextureOnItemProxy::Release()
+{
+ if ( m_pOriginalTexture )
+ {
+ m_pOriginalTexture->Release();
+ m_pOriginalTexture = NULL;
+ }
+}
+
+IMaterial *CCustomTextureOnItemProxy::GetMaterial()
+{
+ return m_pBaseTextureVar->GetOwningMaterial();
+}
+
+void CCustomTextureOnItemProxy::OnBindInternal( CEconItemView *pScriptItem )
+{
+ if ( !m_pBaseTextureVar || !m_pBaseTextureVar->IsTexture() )
+ {
+ return;
+ }
+
+ // Snag the original texture object the first time.
+ // And make sure we're 100% ready to go.
+ if ( m_pOriginalTexture == NULL )
+ {
+ m_pOriginalTexture = m_pBaseTextureVar->GetTextureValue();
+ if ( m_pOriginalTexture == NULL )
+ {
+ return;
+ }
+ if ( m_pOriginalTexture->IsError() )
+ {
+ m_pOriginalTexture = NULL;
+ return;
+ }
+
+ // Success! Let's hang on to this guy
+ m_pOriginalTexture->AddRef();
+ }
+ ITexture *texture = m_pOriginalTexture;
+
+ // Fetch the UGC handle from the item
+ UGCHandle_t ugcHandle = pScriptItem->GetCustomUserTextureID();
+
+ // Are we in a preview window?
+ if ( pScriptItem == g_pPreviewEconItem ) // !KLUDGE!
+ {
+ Assert( g_pPreviewCustomTexture );
+ if ( g_pPreviewCustomTexture )
+ {
+ texture = g_pPreviewCustomTexture;
+
+ // Re-fetch the bits if necessary
+ if ( g_pPreviewCustomTextureDirty )
+ {
+ g_pPreviewCustomTexture->Download();
+ Assert( !g_pPreviewCustomTextureDirty );
+ }
+ }
+ }
+ else if (ugcHandle != 0)
+ {
+
+ SCustomImageCacheEntry *pEntry = CustomTextureCache_FindOrAddByCloudId(ugcHandle);
+ pEntry->Poll();
+ texture = pEntry->m_pTexture; // might be NULL if texture isn't ready yet
+ }
+
+ if ( texture )
+ {
+ m_pBaseTextureVar->SetTextureValue( texture );
+ }
+
+ if ( ToolsEnabled() )
+ {
+ ToolFramework_RecordMaterialParams( GetMaterial() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// The custom texture cache needs to init/shutdown and get some frame ticking
+//-----------------------------------------------------------------------------
+class CCustomTextureToolCache : public CBaseGameSystemPerFrame
+{
+public:
+ CCustomTextureToolCache() {}
+ virtual ~CCustomTextureToolCache() {}
+
+ //
+ // CAutoGameSystemPerFrame overrides
+ //
+ virtual char const *Name()
+ {
+ return "CCustomTextureToolCache";
+ }
+ virtual bool Init()
+ {
+ return true;
+ }
+
+ virtual void Shutdown()
+ {
+
+ // Destroy all the cache entries
+ SCustomImageCacheEntry *pEntry = mruCustomImageEntry;
+ mruCustomImageEntry = NULL;
+ while ( pEntry != NULL )
+ {
+ SCustomImageCacheEntry *pNext = pEntry->m_pNext;
+ delete pEntry;
+ pEntry = pNext;
+ }
+ }
+
+ // At level shutdown, release all of our GPU resources.
+ // We'll still hang on to the bitmap data, since it isn't
+ // that large and is in regular virtual memory which is easily
+ // swapped out if stale. But the video RAM we want to be more
+ // agressive at cleaning out.
+ virtual void LevelShutdownPreEntity()
+ {
+ // Destroy all the cache entries
+ for ( SCustomImageCacheEntry *pEntry = mruCustomImageEntry ; pEntry ; pEntry = pEntry->m_pNext )
+ {
+ pEntry->ReleaseResources();
+ }
+ }
+
+// CAutoGameSystemPerFrame defines different stuff depending on which DLL we're building
+#ifdef CLIENT_DLL
+
+ // Do our frame-time processing after rendering
+ virtual void PostRender()
+ {
+ // !FIXME! Here's where we should scan the list and eject
+ // entries that haven't been used recently to limit the
+ // hardware resources we're using.
+ }
+#else
+ // This file shouldn't be compiled outside of client.dll. Right?
+ #error "Say what?"
+#endif
+};
+
+static CCustomTextureToolCache s_CustomTextureToolCache;
+IGameSystem *CustomTextureToolCacheGameSystem()
+{
+ return &s_CustomTextureToolCache;
+}
+
+
+CApplyCustomTextureJob::CApplyCustomTextureJob( itemid_t nToolItemID, itemid_t nSubjectItemID, const void *pPNGData, int nPNGDataBytes )
+: GCSDK::CGCClientJob( GCClientSystem()->GetGCClient() )
+, m_nToolItemID( nToolItemID )
+, m_nSubjectItemID( nSubjectItemID )
+, m_hCloudID( 0 )
+{
+ m_chRemoteStorageName[0] = '\0';
+ m_bufPNGData.Put( pPNGData, nPNGDataBytes );
+}
+
+bool CApplyCustomTextureJob::BYieldingRunGCJob()
+{
+ YieldingRunJob();
+ CleanUp();
+ return true;
+}
+
+void CApplyCustomTextureJob::CleanUp()
+{
+ // If we had a cloud file, delete it from the logical
+ // cloud filespace. We are using the cloud system really just
+ // to get the file into the UGC system and get a handle to it.
+ // But once it's up there, it really isn't this user. They will
+ // fetch it by UGC handle just like any other user. They paid
+ // for this action, and we don't want it taking up any of their
+ // quota.
+ ISteamRemoteStorage *pRemoteStorage = GetISteamRemoteStorage();
+ if ( pRemoteStorage && m_chRemoteStorageName[0] != '\0' )
+ {
+ pRemoteStorage->FileDelete( m_chRemoteStorageName );
+ }
+}
+
+EResult CApplyCustomTextureJob::YieldingRunJob()
+{
+ EResult result = YieldingFindFileIncacheOrUploadFileToCDN();
+ if ( result != k_EResultOK )
+ {
+ return result;
+ }
+ Assert( m_hCloudID != 0 );
+
+ result = YieldingApplyTool();
+ if ( result != k_EResultOK )
+ {
+ return result;
+ }
+
+ // OK!
+ return k_EResultOK;
+}
+
+EResult CApplyCustomTextureJob::YieldingFindFileIncacheOrUploadFileToCDN()
+{
+
+ int nFileSize = m_bufPNGData.TellPut();
+ Assert( nFileSize <= k_nMaxCustomImageFileSize ); // what the heck is out image converter doing?!
+
+ // Generate the hash
+ char szDigestAscii[ MD5_DIGEST_LENGTH*2 + 4];
+ CalcMD5Ascii( szDigestAscii, m_bufPNGData.Base(), nFileSize );
+
+ // Find or create an existing cache entry
+ SCustomImageCacheEntry *pSelectedCacheEntry = CustomTextureCache_FindOrAddByDigest( szDigestAscii );
+
+ KeyValuesAD pkvMruFile( "StampedItems" );
+
+ {
+ // Load up list of images recently used and uploaded
+ CUtlBuffer listFileData;
+ listFileData.SetBufferType( true, true );
+ if ( BReadSteamRemoteFileToBuffer( listFileData, k_szCustomTextureRecentListFilename ) )
+ {
+ if ( !pkvMruFile->LoadFromBuffer( k_szCustomTextureRecentListFilename, listFileData ) )
+ {
+ pkvMruFile->Clear();
+ }
+ }
+ }
+ KeyValues *pkvMruUploadedImages = pkvMruFile->FindKey( "Uploaded", true );
+
+ // !FIXME! Check for duplicates!
+
+ // Make sure we are ready
+ Assert( pSelectedCacheEntry != NULL );
+ Assert( pSelectedCacheEntry->m_hCloudID == 0 );
+ Assert( strlen(pSelectedCacheEntry->m_szDigestAscii) == MD5_DIGEST_LENGTH*2 );
+ Assert( m_bufPNGData.TellPut() > 0 );
+
+ // Generate filename in the cloud file space. Each user has their own
+ // namespace, and Phil requested that we keep the filenames simple
+ // and easily optimizeable by string table. (I.e. don't use the
+ // hash or something else)
+ //
+ // We *could* just always use the same filename, and each file would
+ // be its own "version." But that doesn't seem to be the proper
+ // spirit of the cloud system. So I'll just use a simple integer name,
+ // based on how many images they have uploaded. It isn't critical what
+ // this logical filename is, because once the GC gets a message to tag the file,
+ // that UGC ID should always refer to that version of the file and can never
+ // be changed or deleted, even if we reuse the filename.
+ int iFileIndex = 1;
+ KeyValues *pKey;
+ for ( pKey = pkvMruUploadedImages->GetFirstTrueSubKey() ; pKey ; pKey = pKey->GetNextTrueSubKey() )
+ {
+ int index = atoi(pKey->GetName());
+ iFileIndex = MAX( iFileIndex, index+1 );
+ }
+ Q_snprintf( m_chRemoteStorageName, sizeof( m_chRemoteStorageName ), "my_custom_images/%d.png", iFileIndex );
+
+ // Write the local copy of the file
+ Msg( "Saving %s to cloud....\n", m_chRemoteStorageName );
+ ISteamRemoteStorage *pRemoteStorage = GetISteamRemoteStorage();
+ if ( !pRemoteStorage || !pRemoteStorage->FileWrite( m_chRemoteStorageName, m_bufPNGData.Base(), m_bufPNGData.TellPut() ) )
+ {
+ Warning( "Failed to save local copy of custom image %s\n", m_chRemoteStorageName);
+ return k_EResultFail;
+ }
+
+ // Share it. This initiates the upload to cloud
+ Msg( "Starting upload of %s to UFS....\n", m_chRemoteStorageName );
+ SteamAPICall_t hFileShareApiCall = pRemoteStorage->FileShare( m_chRemoteStorageName );
+ if ( hFileShareApiCall == k_uAPICallInvalid )
+ {
+ return k_EResultFail;
+ }
+
+ bool bFailed;
+ RemoteStorageFileShareResult_t shareResult;
+ while ( !steamapicontext->SteamUtils()->GetAPICallResult(hFileShareApiCall,
+ &shareResult, sizeof(shareResult), RemoteStorageFileShareResult_t::k_iCallback, &bFailed) )
+ {
+ BYield();
+ }
+
+ if ( bFailed || shareResult.m_eResult != k_EResultOK )
+ {
+ Warning( "Custom texture uploaded to cloud FAILED\n" );
+ return k_EResultFail;
+ }
+
+ Msg( "Custom texture uploaded to cloud completed OK, assigned UGC ID %08X%08X\n", (uint32)(shareResult.m_hFile >> 32), (uint32)(shareResult.m_hFile) );
+
+ // Remember the handle to the cloud file
+ m_hCloudID = pSelectedCacheEntry->m_hCloudID = shareResult.m_hFile;
+
+ // Update the MRU list
+ pKey = pkvMruUploadedImages->GetFirstTrueSubKey();
+ while ( pKey )
+ {
+ int index = atoi(pKey->GetName());
+ int mruValue = pKey->GetInt( "mru", 0 );
+ const char *entryDigsetAscii = pKey->GetString("md5", "");
+ UGCHandle_t ugcID = pKey->GetUint64( "ugcid", 0);
+ if ( index <= 0 || mruValue <= 0 || strlen(entryDigsetAscii) != MD5_DIGEST_LENGTH*2 || ugcID == 0 )
+ {
+ // Bah! Bogus data!
+ Assert(false);
+ continue;
+ }
+
+ // Is this the one they selected?
+ if ( ugcID == pSelectedCacheEntry->m_hCloudID )
+ {
+
+ // This *can* happen if the list file gets lost and they reuse an image. It means we are wasting
+ // some of their cloud quota, but should be rare, and it's harmless.
+ Assert( !Q_stricmp(entryDigsetAscii, pSelectedCacheEntry->m_szDigestAscii) );
+ break;
+ }
+
+ pKey = pKey->GetNextTrueSubKey();
+ }
+
+ // Found it?
+ int oldIndex = 0x7fffffff;
+ if ( pKey )
+ {
+
+ // Renumber them in MRU order
+ oldIndex = pKey->GetInt( "mru", 1 );
+ }
+ else
+ {
+
+ // Create a new key
+ pKey = pkvMruUploadedImages->CreateNewKey();
+
+ // Remember hash and cloud file location in subkeys
+ pKey->SetString( "md5", pSelectedCacheEntry->m_szDigestAscii );
+ pKey->SetUint64( "ugcid", pSelectedCacheEntry->m_hCloudID );
+ //pKey->SetString( "remoteStorageName", m_chSelectedRemoteStorageNameBase );
+ }
+ for ( KeyValues *p = pkvMruUploadedImages->GetFirstTrueSubKey() ; p ; p = p->GetNextTrueSubKey() )
+ {
+ if ( p != pKey )
+ {
+ int mruValue = p->GetInt( "mru", 0 );
+ Assert( mruValue > 0 );
+ if (mruValue < oldIndex)
+ {
+ p->SetInt( "mru", mruValue+1 );
+ }
+ }
+ }
+
+ pKey->SetInt( "mru", 1);
+
+ // Re-save the cloud-backed MRU list file
+ Msg( "Saving MRU list file %s\n", k_szCustomTextureRecentListFilename );
+ if ( pRemoteStorage )
+ {
+ CUtlBuffer listFileData;
+ listFileData.SetBufferType( true, true );
+ pkvMruFile->RecursiveSaveToFile( listFileData, 0 );
+ pRemoteStorage->FileWrite( k_szCustomTextureRecentListFilename, listFileData.Base(), listFileData.TellPut() );
+ }
+
+ return k_EResultOK;
+}
+
+EResult CApplyCustomTextureJob::YieldingApplyTool()
+{
+
+ Msg( "Sending tool request to GC.\n" );
+
+ // At this point, we need to know the cloud ID and hash of the image we are applying
+ Assert( m_hCloudID != 0 );
+
+ // Send the message to the GC
+ GCSDK::CGCMsg< MsgGCCustomizeItemTexture_t > msg( k_EMsgGCCustomizeItemTexture );
+ msg.Body().m_unToolItemID = m_nToolItemID;
+ msg.Body().m_unSubjectItemID = m_nSubjectItemID;
+ msg.Body().m_unImageUGCHandle = m_hCloudID;
+
+ GCSDK::CGCMsg<MsgGCStandardResponse_t> msgReply;
+ if ( !BYldSendMessageAndGetReply( msg, 10, &msgReply, k_EMsgGCCustomizeItemTextureResponse ) )
+ {
+ Warning( "Customize texture tool failed: Did not get reply from GC\n" );
+ return k_EResultTimeout;
+ }
+
+ // OK!
+ InventoryManager()->ShowItemsPickedUp( true );
+ return k_EResultOK;
+};
+
diff --git a/game/client/econ/tool_items/custom_texture_cache.h b/game/client/econ/tool_items/custom_texture_cache.h
new file mode 100644
index 0000000..c26b94e
--- /dev/null
+++ b/game/client/econ/tool_items/custom_texture_cache.h
@@ -0,0 +1,83 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef CUSTOM_TEXTURE_CACHE_H
+#define CUSTOM_TEXTURE_CACHE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#ifndef GC_CLIENTSYSTEM_H
+ #include "gc_clientsystem.h"
+#endif
+
+class IGameSystem;
+class ITexture;
+class CEconItemView;
+
+/// Given a UGC cloud ID of a custom image, return a VGUI texture handle
+/// that can be used for drawing. Returns 0 on failure
+int GetCustomTextureGuiHandle( uint64 hCloudId );
+
+//-----------------------------------------------------------------------------
+// Purpose: Job to do the async work of uploading the file to the CDN (if
+// necessary) and sending the tool request message
+//-----------------------------------------------------------------------------
+class CApplyCustomTextureJob : public GCSDK::CGCClientJob
+{
+public:
+
+ CApplyCustomTextureJob( itemid_t nToolItemID, itemid_t nSubjectItemID, const void *pPNGData, int nPNGDataBytes );
+
+protected:
+ char m_chRemoteStorageName[ MAX_PATH ];
+
+ virtual bool BYieldingRunGCJob();
+ virtual EResult YieldingRunJob();
+ virtual EResult YieldingFindFileIncacheOrUploadFileToCDN();
+ virtual EResult YieldingApplyTool();
+
+ /// The file data, in PNG format
+ CUtlBuffer m_bufPNGData;
+
+ /// Item that we are applying the texture onto
+ itemid_t m_nSubjectItemID;
+
+ /// Tool that is being applied and will be consumed
+ itemid_t m_nToolItemID;
+
+ /// Cloud file ID
+ uint64 m_hCloudID;
+
+private:
+ void CleanUp();
+};
+
+/// get interface to the game system responsible for managing the custom texture cache
+IGameSystem *CustomTextureToolCacheGameSystem();
+
+/// A few internal things that really shouldn't be public
+namespace CustomTextureSystem
+{
+
+/// If we're auditioning (while selecting a file to apply on a model),
+/// what texture should we display?
+extern ITexture *g_pPreviewCustomTexture;
+
+/// What is the econ item that is being auditioned and so should
+/// use the preview texture
+extern CEconItemView *g_pPreviewEconItem;
+
+/// Do we need to update the bits in the rendering system?
+extern bool g_pPreviewCustomTextureDirty;
+
+extern const char k_rchCustomTextureFilterPreviewImageName[];
+extern const char k_rchCustomTextureFilterPreviewTextureName[];
+
+}
+
+#endif // CUSTOM_TEXTURE_CACHE_H
diff --git a/game/client/econ/tool_items/custom_texture_tool.cpp b/game/client/econ/tool_items/custom_texture_tool.cpp
new file mode 100644
index 0000000..7f9e14a
--- /dev/null
+++ b/game/client/econ/tool_items/custom_texture_tool.cpp
@@ -0,0 +1,2766 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+
+// for the tool
+#include "econ_gcmessages.h"
+#include "econ_item_system.h"
+#include "econ_item_constants.h"
+#include "tool_items.h"
+#include "imageutils.h"
+#include "econ_ui.h"
+#include "econ_item_inventory.h"
+#include "econ_item_tools.h"
+#include "checksum_md5.h"
+#include "gc_clientsystem.h"
+#include "materialsystem/itexture.h"
+#include "pixelwriter.h"
+
+#include "filesystem.h"
+
+// for UI
+#include "confirm_dialog.h"
+#include "vgui_controls/EditablePanel.h"
+#include "vgui_controls/FileOpenDialog.h"
+#include "vgui_controls/ImagePanel.h"
+#include "vgui_controls/TextEntry.h"
+#include "vgui_controls/RadioButton.h"
+#include "vgui_controls/ComboBox.h"
+#include "vgui_controls/Slider.h"
+#include "vgui/Cursor.h"
+#include "vgui/IInput.h"
+#include "vgui/ISurface.h"
+#include "vgui/IImage.h"
+#include "vgui/IBorder.h"
+#include "VGuiMatSurface/IMatSystemSurface.h"
+#include "bitmap/tgawriter.h"
+#include "bitmap/bitmap.h"
+#include "vgui_bitmappanel.h"
+#include "tool_items/custom_texture_cache.h"
+#include "util_shared.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+using namespace CustomTextureSystem;
+
+
+// Turn this on to run the filters on a bunch of test images when the dialog is opened
+//#define TEST_FILTERS
+
+#define DEFINE_BLEND(code) \
+ for (int y = 0 ; y < imgSource.Height() ; ++y ) \
+ { \
+ for (int x = 0 ; x < imgSource.Width() ; ++x ) \
+ { \
+ Color sc = imgSource.GetColor( x,y ); \
+ Color dc = imgDest.GetColor( x,y ); \
+ float sr = (float)sc.r()/255.0f, sg = (float)sc.g()/255.0f, sb = (float)sc.b()/255.0f, sa = (float)sc.a()/255.0f; \
+ float dr = (float)dc.r()/255.0f, dg = (float)dc.g()/255.0f, db = (float)dc.b()/255.0f, da = (float)dc.a()/255.0f; \
+ float blendPct = sa * flOpacity; \
+ code \
+ imgDest.SetColor( x,y, FloatRGBAToColor( dr*255.0f, dg*255.0f, db*255.0f, da*255.0f ) ); \
+ } \
+ }
+
+static void DoNormalBlend( const Bitmap_t &imgSource, Bitmap_t &imgDest, float flOpacity )
+{
+ DEFINE_BLEND(
+ dr += (sr - dr) * blendPct;
+ dg += (sg - dg) * blendPct;
+ db += (sb - db) * blendPct;
+ )
+}
+
+static void DoMultiplyBlend( const Bitmap_t &imgSource, Bitmap_t &imgDest, float flOpacity )
+{
+ DEFINE_BLEND(
+ dr += (dr*sr - dr) * blendPct;
+ dg += (dg*sg - dg) * blendPct;
+ db += (db*sb - db) * blendPct;
+ )
+}
+
+static inline float screen( float a, float b )
+{
+ return 1.0f - (1.0f-a)*(1.0f-b);
+}
+
+static void DoScreenBlend( const Bitmap_t &imgSource, Bitmap_t &imgDest, float flOpacity )
+{
+ DEFINE_BLEND(
+ dr += (screen(dr,sr) - dr) * blendPct;
+ dg += (screen(dg,sg) - dg) * blendPct;
+ db += (screen(db,sb) - db) * blendPct;
+ )
+}
+
+static inline float overlay( float a, float b )
+{
+ if ( a < .5f )
+ {
+ return a * b * 2.0f;
+ }
+ float t = a * 2.0f - 1.0f;
+ return screen( t, b );
+}
+
+static void DoOverlayBlend( const Bitmap_t &imgSource, Bitmap_t &imgDest, float flOpacity )
+{
+ DEFINE_BLEND(
+ dr += (overlay(dr,sr) - dr) * blendPct;
+ dg += (overlay(dg,sg) - dg) * blendPct;
+ db += (overlay(db,sb) - db) * blendPct;
+ )
+}
+
+static void DoReplaceAlphaBlend( const Bitmap_t &imgSource, Bitmap_t &imgDest, float flOpacity )
+{
+ DEFINE_BLEND(
+ float k = (sr + sb + sg) / 3.0f;
+ da += (k - da) * blendPct;
+ )
+}
+
+// Custom compositing blend operations. Mostly these are direct translations of standard Photoshop operations.
+// For most operations, the source alpha used as per-pixel blend factor (multiplied by the layer opacity), and
+// the dest alpha is just copied
+enum ELayerBlendOp
+{
+ eLayerBlendOp_Invalid, // Debugging placeholder value
+ eLayerBlendOp_Normal, // Regular blend (lerp)
+ eLayerBlendOp_Multiply, // Multiply color channels
+ eLayerBlendOp_Screen, // 1 - (1-A) * (1-B)
+ eLayerBlendOp_Overlay, // Multiply or screen, depending on source
+ eLayerBlendOp_ReplaceAlpha, // Blend the source alpha channel with the greyscale value from the layer. Color channel is not modified
+};
+
+/// A custom compositing step
+struct SDecalBlendLayer
+{
+
+ /// Which operation to perform?
+ ELayerBlendOp eLayerOp;
+
+ /// The image data
+ Bitmap_t m_image;
+
+ /// Opacity multiplier. The full blend color is calculated by performing the blend
+ /// operation ignoring opacity. Then this result is lerped with the dest fragment by
+ /// the effective blend factor. The effective per-pixel blend factor is taken as the
+ /// source alpha times this value.
+ float m_fLayerOpacity;
+
+ /// Parse from keyvalues.
+ bool FromKV( KeyValues *pkvLayerBlock, CUtlString &errMsg )
+ {
+ const char *op = pkvLayerBlock->GetString( "op", "(none)" );
+ if ( !Q_stricmp( op, "normal" ) ) eLayerOp = eLayerBlendOp_Normal;
+ else if ( !Q_stricmp( op, "multiply" ) ) eLayerOp = eLayerBlendOp_Multiply;
+ else if ( !Q_stricmp( op, "screen" ) ) eLayerOp = eLayerBlendOp_Screen;
+ else if ( !Q_stricmp( op, "overlay" ) ) eLayerOp = eLayerBlendOp_Overlay;
+ else if ( !Q_stricmp( op, "ReplaceAlpha" ) ) eLayerOp = eLayerBlendOp_ReplaceAlpha;
+ else
+ {
+ errMsg.Format( "Invalid blend operation '%s'", op );
+ return false;
+ }
+
+ const char *pszImageFilename = pkvLayerBlock->GetString( "image", NULL );
+ if ( pszImageFilename == NULL )
+ {
+ errMsg = "Must specify 'image'";
+ return false;
+ }
+ if ( ImgUtl_LoadBitmap( pszImageFilename, m_image ) != CE_SUCCESS )
+ {
+ errMsg.Format( "Can't load image '%s'", pszImageFilename );
+ return false;
+ }
+
+ m_fLayerOpacity = pkvLayerBlock->GetFloat( "opacity", 1.0f );
+
+ return true;
+ }
+
+ /// Apply the operation
+ void Apply( Bitmap_t &imgDest ) const
+ {
+ if ( !m_image.IsValid() || !imgDest.IsValid() || imgDest.Width() != m_image.Width() || imgDest.Height() != m_image.Height() )
+ {
+ Assert( m_image.IsValid() );
+ Assert( imgDest.IsValid() );
+ Assert( imgDest.Width() == m_image.Width() );
+ Assert( imgDest.Height() == m_image.Height() );
+ return;
+ }
+
+ switch ( eLayerOp )
+ {
+ default:
+ case eLayerBlendOp_Invalid:
+ Assert( !"Bogus blend op!" );
+ case eLayerBlendOp_Normal:
+ DoNormalBlend( m_image, imgDest, m_fLayerOpacity );
+ break;
+ case eLayerBlendOp_Multiply:
+ DoMultiplyBlend( m_image, imgDest, m_fLayerOpacity );
+ break;
+ case eLayerBlendOp_Screen:
+ DoScreenBlend( m_image, imgDest, m_fLayerOpacity );
+ break;
+ case eLayerBlendOp_Overlay:
+ DoOverlayBlend( m_image, imgDest, m_fLayerOpacity );
+ break;
+ case eLayerBlendOp_ReplaceAlpha:
+ DoReplaceAlphaBlend( m_image, imgDest, m_fLayerOpacity );
+ break;
+ }
+ }
+};
+
+// Note: uses a non-linear non-perceptual color space. But it will be good enough,
+// probably
+inline int ApproxColorDistSq( const Color &a, const Color &b )
+{
+ int dr = (int)a.r() - (int)b.r();
+ int dg = (int)a.g() - (int)b.g();
+ int db = (int)a.b() - (int)b.b();
+ return dr*dr + dg*dg + db*db;
+}
+
+// Return cheesy color distance calculation, approximately normalized from 0...1
+inline float ApproxColorDist( const Color &a, const Color &b )
+{
+ return sqrt( (float)ApproxColorDistSq( a, b ) ) * ( 1.0f / 441.67f );
+}
+
+// Convert linear RGB -> XYZ color space.
+Vector LinearRGBToXYZ( const Vector &rgb )
+{
+
+ // http://en.wikipedia.org/wiki/SRGB
+ Vector xyz;
+ xyz.x = rgb.x * 0.4124 + rgb.y*0.3576 + rgb.z*0.1805;
+ xyz.y = rgb.x * 0.2126 + rgb.y*0.7152 + rgb.z*0.0722;
+ xyz.z = rgb.x * 0.0193 + rgb.y*0.1192 + rgb.z*0.9505;
+ return xyz;
+}
+
+inline float lab_f( float t )
+{
+ if ( t > (6.0/29.0)*(6.0/29.0)*(6.0/29.0) )
+ {
+ return pow( t, .333333f );
+ }
+ return ( (1.0f/3.0f) * (29.0f/6.0f) * (29.0f/6.0f) ) * t + (4.0f/29.0f);
+}
+
+// Convert CIE XYZ -> L*a*b*
+Vector XYZToLab( const Vector &xyz )
+{
+
+ // http://en.wikipedia.org/wiki/Lab_color_space
+ const float X_n = 0.9505;
+ const float Y_n = 1.0000;
+ const float Z_n = 1.0890;
+
+ float f_X = lab_f( xyz.x / X_n );
+ float f_Y = lab_f( xyz.y / Y_n );
+ float f_Z = lab_f( xyz.z / Z_n );
+
+ Vector lab;
+ lab.x = 116.0f*f_Y - 16.0f; // L*
+ lab.y = 500.0f * ( f_X - f_Y ); // a*
+ lab.z = 200.0f * ( f_Y - f_Z ); // b*
+ return lab;
+}
+
+// Convert texture-space RGB values to linear RGB space
+Vector TextureToLinearRGB( Color c )
+{
+ Vector rgb;
+ rgb.x = SrgbGammaToLinear( (float)c.r() / 255.0f );
+ rgb.y = SrgbGammaToLinear( (float)c.g() / 255.0f );
+ rgb.z = SrgbGammaToLinear( (float)c.b() / 255.0f );
+ return rgb;
+}
+
+// Convert texture-space RGB values to perceptually linear L*a*b* space
+Vector TextureToLab( Color c )
+{
+ Vector linearRGB = TextureToLinearRGB( c );
+ Vector xyz = LinearRGBToXYZ( linearRGB );
+ return XYZToLab( xyz );
+}
+
+static void SymmetricNearestNeighborFilter( const Bitmap_t &imgSrc, Bitmap_t &imgDest, int radius, float amount = 1.0f )
+{
+
+ // Make sure image is allocated properly
+ int nWidth = imgSrc.Width();
+ int nHeight = imgSrc.Height();
+ imgDest.Init( nWidth, nHeight, IMAGE_FORMAT_RGBA8888 );
+
+ float flWeightBias = (2 + radius + radius );
+ int filteredBlendWeight = int(amount * 256.0f);
+ int originalBlendWeight = 256 - filteredBlendWeight;
+
+ // For each dest pixel
+ for ( int y = 0 ; y < nHeight ; ++y )
+ {
+ for ( int x = 0 ; x < nWidth ; ++x )
+ {
+ Color c = imgSrc.GetColor( x, y );
+
+ // Iterate over half of the kernel. (Doesn't matter which half.)
+ // Kernel pixels are examined in opposing pairs
+ Vector4D sum(0,0,0,0);
+ float flTotalWeight = 0.0f;
+ for (int ry = 0 ; ry <= radius ; ++ry )
+ {
+ int sy1 = clamp(y + ry, 0, nHeight-1);
+ int sy2 = clamp(y - ry, 0, nHeight-1);
+ for (int rx = (ry == 0) ? 0 : -radius ; rx <= radius ; ++rx )
+ {
+ int sx1 = clamp(x + rx, 0, nWidth-1);
+ int sx2 = clamp(x - rx, 0, nWidth-1);
+
+ Color s1 = imgSrc.GetColor( sx1, sy1 );
+ Color s2 = imgSrc.GetColor( sx2, sy2 );
+
+ // Calculate difference. Here, maybe we should be using
+ // a perceptual difference in linear color space. Who cares.
+ int d1 = ApproxColorDistSq( c, s1 );
+ int d2 = ApproxColorDistSq( c, s2 );
+
+ float weight = flWeightBias - fabs((float)ry) - fabs((float)rx);
+ if ( d1 < d2 )
+ {
+ sum.x += (float)s1.r() * weight;
+ sum.y += (float)s1.g() * weight;
+ sum.z += (float)s1.b() * weight;
+ sum.w += (float)s1.a() * weight;
+ }
+ else
+ {
+ sum.x += (float)s2.r() * weight;
+ sum.y += (float)s2.g() * weight;
+ sum.z += (float)s2.b() * weight;
+ sum.w += (float)s2.a() * weight;
+ }
+ flTotalWeight += weight;
+ }
+ }
+
+ sum /= flTotalWeight;
+ int filterR = (int)clamp(sum.x, 0.0f, 255.0f);
+ int filterG = (int)clamp(sum.y, 0.0f, 255.0f);
+ int filterB = (int)clamp(sum.z, 0.0f, 255.0f);
+ int filterA = (int)clamp(sum.w, 0.0f, 255.0f);
+ Color result(
+ (filterR*filteredBlendWeight + c.r()*originalBlendWeight) >> 8,
+ (filterG*filteredBlendWeight + c.g()*originalBlendWeight) >> 8,
+ (filterB*filteredBlendWeight + c.b()*originalBlendWeight) >> 8,
+ (filterA*filteredBlendWeight + c.a()*originalBlendWeight) >> 8
+ );
+ imgDest.SetColor( x, y, result );
+ }
+ }
+}
+
+static void BilateralFilter( const Bitmap_t &imgSrc, Bitmap_t &imgDest, int radius, float colorDiffThreshold, float amount = 1.0f )
+{
+
+ // Make sure image is allocated properly
+ int nWidth = imgSrc.Width();
+ int nHeight = imgSrc.Height();
+ imgDest.Init( nWidth, nHeight, IMAGE_FORMAT_RGBA8888 );
+
+ float flWeightBias = (2 + radius + radius );
+ int filteredBlendWeight = int(amount * 256.0f);
+ int originalBlendWeight = 256 - filteredBlendWeight;
+
+ // For each dest pixel
+ for ( int y = 0 ; y < nHeight ; ++y )
+ {
+ for ( int x = 0 ; x < nWidth ; ++x )
+ {
+ Color c = imgSrc.GetColor( x, y );
+
+ // Iterate over the kernel
+ Vector4D sum(0,0,0,0);
+ float flTotalWeight = 0.0f;
+ for (int ry = -radius ; ry <= radius ; ++ry )
+ {
+ int sy = clamp(y + ry, 0, nHeight-1);
+ for (int rx = -radius ; rx <= radius ; ++rx )
+ {
+ int sx = clamp(x + rx, 0, nWidth-1);
+
+ Color s = imgSrc.GetColor( sx, sy );
+
+ // Calculate difference. Here, maybe we should be using
+ // a perceptual difference in linear color space. Who cares.
+ float colorDist = ApproxColorDist( c, s );
+
+ // Geometry-based weight
+ float geomWeight = flWeightBias - fabs((float)ry) - fabs((float)rx);
+
+ // Distance-based weight
+ float diffWeight = 1.0f - colorDist - colorDiffThreshold;
+
+ // Total weight
+ float weight = geomWeight * diffWeight;
+ if ( weight > 0.0f )
+ {
+ sum.x += (float)s.r() * weight;
+ sum.y += (float)s.g() * weight;
+ sum.z += (float)s.b() * weight;
+ sum.w += (float)s.a() * weight;
+ flTotalWeight += weight;
+ }
+ }
+ }
+
+ sum /= flTotalWeight;
+ int filterR = (int)clamp(sum.x, 0.0f, 255.0f);
+ int filterG = (int)clamp(sum.y, 0.0f, 255.0f);
+ int filterB = (int)clamp(sum.z, 0.0f, 255.0f);
+ int filterA = (int)clamp(sum.w, 0.0f, 255.0f);
+ Color result(
+ (filterR*filteredBlendWeight + c.r()*originalBlendWeight) >> 8,
+ (filterG*filteredBlendWeight + c.g()*originalBlendWeight) >> 8,
+ (filterB*filteredBlendWeight + c.b()*originalBlendWeight) >> 8,
+ (filterA*filteredBlendWeight + c.a()*originalBlendWeight) >> 8
+ );
+ imgDest.SetColor( x, y, result );
+ }
+ }
+}
+
+// Scan image and replace each pixel with the closest matching swatch
+static void ColorReplace( const Bitmap_t &imgSrc, Bitmap_t &imgDest, int nSwatchCount, const Color *pSwatchList, float amount = 1.0f, const float *pSwatchWeightList = NULL )
+{
+ Assert( nSwatchCount >= 1 );
+
+ CUtlVector<Vector> swatchLab;
+ for ( int i = 0 ; i < nSwatchCount ; ++i )
+ {
+ swatchLab.AddToTail( TextureToLab( pSwatchList[i] ) );
+ }
+
+ // Make sure image is allocated properly
+ int nWidth = imgSrc.Width();
+ int nHeight = imgSrc.Height();
+ imgDest.Init( nWidth, nHeight, IMAGE_FORMAT_RGBA8888 );
+
+ CUtlVector<float> vecDistScale;
+ if ( pSwatchWeightList )
+ {
+ float total = 0.0f;
+ for (int i = 0 ; i < nSwatchCount ; ++i)
+ {
+ total += pSwatchWeightList[i];
+ }
+ total *= 1.05f;
+ for (int i = 0 ; i < nSwatchCount ; ++i)
+ {
+ vecDistScale.AddToTail( total - pSwatchWeightList[i] );
+ }
+ }
+ else
+ {
+ for (int i = 0 ; i < nSwatchCount ; ++i)
+ {
+ vecDistScale.AddToTail( 1.0f );
+ }
+ }
+
+ // For each dest pixel
+ for ( int y = 0 ; y < nHeight ; ++y )
+ {
+ for ( int x = 0 ; x < nWidth ; ++x )
+ {
+ // Fetch source color
+ Color c = imgSrc.GetColor( x, y );
+ Vector lab = TextureToLab( c );
+
+ // Search for the closest matching swatch in the palette
+ Color closestSwatchColor = pSwatchList[0];
+ //int bestDist = ApproxColorDistSq( c, closestSwatchColor );
+ float bestDist = lab.DistTo( swatchLab[0] ) * vecDistScale[0];
+ for ( int i = 1 ; i < nSwatchCount ; ++i )
+ {
+ //int dist = ApproxColorDistSq( c, pSwatchList[i] );
+ float dist = lab.DistTo( swatchLab[i] ) * vecDistScale[i];
+ if ( dist < bestDist )
+ {
+ bestDist = dist;
+ closestSwatchColor = pSwatchList[i];
+ }
+ }
+
+ imgDest.SetColor( x, y, LerpColor( c, closestSwatchColor, amount ) );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Custom control for the gradient editing
+//-----------------------------------------------------------------------------
+class CustomTextureStencilGradientMapWidget : public vgui::Panel
+{
+ DECLARE_CLASS_SIMPLE( CustomTextureStencilGradientMapWidget, vgui::Panel );
+
+public:
+ CustomTextureStencilGradientMapWidget(vgui::Panel *parent, const char *panelName);
+
+ // Slam range count, forcing nobs to be spaced evenly
+ void InitRangeCount( int nRangeCount );
+
+ // Set new number of ranges, attempting to adjust nob positions in a "reasonable" way
+ void AdjustRangeCount( int nRangeCount );
+ int GetRangeCount() const { return m_nRangeCount; }
+ int GetNobCount() const { return m_nRangeCount-1; }
+ int GetNobValue( int nNobIndex ) const; // allows virtual "nobs" at indices -1 and m_nRangeCount
+ void SetNobValue( int nNobIndex, int value ); // clamp to adjacent nobs
+ void SlamNobValue( int nNobIndex, int value ); // force nob to particular value, and don't check it
+ void SetRangeColors( const Color *rColors )
+ {
+ memcpy( m_colRangeColor, rColors, m_nRangeCount*sizeof(m_colRangeColor[0]) );
+ ComputeGradient();
+ }
+
+ /// Convert local x coordinate to value
+ int LocalXToVal( int x, bool bClamp = true );
+
+ /// Convert value to local x coordinate
+ int ValToLocalX( int value, bool bClamp = true );
+
+ enum { k_nMaxRangeCount = 4 };
+
+ virtual void OnCursorMoved(int x, int y);
+ virtual void OnMousePressed(vgui::MouseCode code);
+ virtual void OnMouseDoublePressed(vgui::MouseCode code);
+ virtual void OnMouseReleased(vgui::MouseCode code);
+
+ Color m_colorGradient[ 256 ];
+
+protected:
+ virtual void Paint();
+ virtual void PaintBackground();
+ virtual void ApplySchemeSettings(vgui::IScheme *pScheme);
+
+
+ int m_iDraggedNob; // -1 if none
+ int m_nRangeCount;
+ int m_nNobVal[k_nMaxRangeCount-1];
+ Color m_colRangeColor[k_nMaxRangeCount];
+ int m_iNobSizeX;
+ int m_iNobSizeY;
+ int m_iNobRelPosY;
+ int m_iRibbonSizeY;
+ int m_iRibbonRelPosY;
+ int m_iMinVal;
+ int m_iMaxVal;
+ int m_iNobValCushion; // closest that we allow two nobs to be together
+ int m_iClickOffsetX;
+
+ // size (in intensity values on 255 scale) of transition band centered on nob
+ int m_iTransitionBandSize;
+
+ // The regions between the nobs are not *quite* a sold color. They have a slight
+ // gradient in them. This value control the max difference in the ends of this
+ // gradient
+ int m_iRegionGradientRange;
+
+ Color m_TickColor;
+ Color m_TrackColor;
+
+ Color m_DisabledTextColor1;
+ Color m_DisabledTextColor2;
+
+ vgui::IBorder *_sliderBorder;
+ vgui::IBorder *_insetBorder;
+
+ void SendSliderMovedMessage();
+
+ /// Mouse hit testing. Returns index of the nob under the cursor, or
+ /// -1 if none. Coords are local
+ int HitTest( int x, int y, int &outOffsetX );
+
+ /// Fetch local rectangle for given nob
+ void GetNobRect( int iNobIndex, int &x1, int &y1, int &xs, int &ys );
+
+ void GetColorRibbonRect( int &x1, int &y1, int &xs, int &ys );
+
+ void ComputeSizes();
+ void ComputeGradient();
+
+ bool m_bClickOnRanges;
+};
+
+DECLARE_BUILD_FACTORY( CustomTextureStencilGradientMapWidget );
+
+//-----------------------------------------------------------------------------
+CustomTextureStencilGradientMapWidget::CustomTextureStencilGradientMapWidget(Panel *parent, const char *panelName )
+: Panel(parent, panelName)
+{
+ m_iDraggedNob = -1;
+ m_nRangeCount = 4;
+ m_nNobVal[0] = 64;
+ m_nNobVal[1] = 128;
+ m_nNobVal[2] = 192;
+ m_colRangeColor[0] = Color(183,224,252,255);
+ m_colRangeColor[1] = Color(83,109,205,255);
+ m_colRangeColor[2] = Color(98,48,43,255);
+ m_colRangeColor[3] = Color(234,198,113,255);
+ m_iNobSizeX = 4;
+ m_iNobSizeY = 4;
+ m_iNobRelPosY = 0;
+ m_iRibbonSizeY = 0;
+ m_iRibbonRelPosY = 4;
+ m_iMinVal = 0;
+ m_iMaxVal = 255;
+ m_iNobValCushion = 8;
+ m_iClickOffsetX = 0;
+ m_bClickOnRanges = false;
+
+ m_iTransitionBandSize = 8;
+ m_iRegionGradientRange = 8;
+
+ _sliderBorder = NULL;
+ _insetBorder = NULL;
+
+ //AddActionSignalTarget( parent );
+ SetBlockDragChaining( true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Send a message to interested parties when the slider moves
+//-----------------------------------------------------------------------------
+void CustomTextureStencilGradientMapWidget::SendSliderMovedMessage()
+{
+ // send a changed message
+ KeyValues *pParams = new KeyValues("SliderMoved");
+ pParams->SetPtr( "panel", this );
+ PostActionSignal( pParams );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CustomTextureStencilGradientMapWidget::ApplySchemeSettings(vgui::IScheme *pScheme)
+{
+ BaseClass::ApplySchemeSettings(pScheme);
+
+ SetFgColor(GetSchemeColor("Slider.NobColor", pScheme));
+ // this line is useful for debugging
+ //SetBgColor(GetSchemeColor("0 0 0 255"));
+
+ m_TickColor = pScheme->GetColor( "Slider.TextColor", GetFgColor() );
+ m_TrackColor = pScheme->GetColor( "Slider.TrackColor", GetFgColor() );
+
+ m_DisabledTextColor1 = pScheme->GetColor( "Slider.DisabledTextColor1", GetFgColor() );
+ m_DisabledTextColor2 = pScheme->GetColor( "Slider.DisabledTextColor2", GetFgColor() );
+
+ _sliderBorder = pScheme->GetBorder("ButtonBorder");
+ _insetBorder = pScheme->GetBorder("ButtonDepressedBorder");
+
+ ComputeSizes();
+ ComputeGradient();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Draw everything on screen
+//-----------------------------------------------------------------------------
+void CustomTextureStencilGradientMapWidget::Paint()
+{
+// DrawTicks();
+//
+// DrawTickLabels();
+//
+// // Draw nob last so it draws over ticks.
+// DrawNob();
+
+ // Draw nobs last
+ for ( int i = 0 ; i < GetNobCount() ; ++i )
+ {
+ int x1, y1, xs, ys;
+ GetNobRect( i, x1, y1, xs, ys );
+
+ Color col = GetFgColor();
+ g_pMatSystemSurface->DrawSetColor(col);
+ g_pMatSystemSurface->DrawFilledRect(
+ x1,
+ y1,
+ x1+xs,
+ y1+ys
+ );
+
+ }
+
+// // border
+// if (_sliderBorder)
+// {
+// _sliderBorder->Paint(
+// _nobPos[0],
+// y + tall / 2 - nobheight / 2,
+// _nobPos[1],
+// y + tall / 2 + nobheight / 2);
+// }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Draw the slider track
+//-----------------------------------------------------------------------------
+void CustomTextureStencilGradientMapWidget::PaintBackground()
+{
+ BaseClass::PaintBackground();
+
+ int x1, y1, xs, ys;
+ GetColorRibbonRect( x1, y1, xs, ys );
+
+ // This is utterly terrible. It could be drawn a LOT more efficiently!
+ for ( int x = 0 ; x < xs ; ++x )
+ {
+ int v = x * 256 / xs;
+ vgui::surface()->DrawSetColor( m_colorGradient[v] );
+ vgui::surface()->DrawFilledRect( x1 + x, y1, x1 + x + 1, y1 + ys );
+ }
+
+// int x, y;
+// int wide,tall;
+//
+// GetTrackRect( x, y, wide, tall );
+//
+// surface()->DrawSetColor( m_TrackColor );
+// surface()->DrawFilledRect( x, y, x + wide, y + tall );
+// if (_insetBorder)
+// {
+// _insetBorder->Paint( x, y, x + wide, y + tall );
+// }
+}
+
+void CustomTextureStencilGradientMapWidget::ComputeGradient()
+{
+
+ struct GradientInterpolationPoint
+ {
+ int m_iVal;
+ Color m_color;
+ };
+
+ GradientInterpolationPoint rGradPoints[ k_nMaxRangeCount * 2 ];
+ int nGradPoints = 0;
+
+ // Put two interpolation points per region.
+ for ( int iRange = 0 ; iRange < m_nRangeCount ; ++iRange )
+ {
+ // Get nob values on either side
+ // of the region
+ int lVal = GetNobValue( iRange-1 );
+ int rVal = GetNobValue( iRange );
+
+ // Push them together slightly, to create a small gradient band
+ // around the nobs
+ int d = rVal - lVal;
+ if ( d > 2 )
+ {
+ int iPush = MIN ( d, m_iTransitionBandSize ) / 2;
+ if ( iRange > 0 )
+ {
+ lVal += iPush;
+ }
+ if ( iRange < m_nRangeCount-1 )
+ {
+ rVal -= iPush;
+ }
+ }
+
+ Color lColor = m_colRangeColor[iRange];
+ Color rColor = m_colRangeColor[iRange];
+ // !FIXME! Nudge color towards neighbors
+
+ // Insert interpolation points
+ Assert( nGradPoints+2 <= ARRAYSIZE( rGradPoints ) );
+ rGradPoints[ nGradPoints ].m_iVal = lVal;
+ rGradPoints[ nGradPoints ].m_color = lColor;
+ ++nGradPoints;
+ rGradPoints[ nGradPoints ].m_iVal = rVal;
+ rGradPoints[ nGradPoints ].m_color = rColor;
+ ++nGradPoints;
+ }
+
+ // Now fill in gradient
+ Assert( m_iMinVal == 0 );
+ Assert( m_iMaxVal == 255 );
+ COMPILE_TIME_ASSERT( ARRAYSIZE( m_colorGradient ) == 256 );
+
+ int iRightIndex = 1; // current interpolation point on right hand side
+ for ( int i = 0 ; i < 256 ; ++i )
+ {
+ while ( i >= rGradPoints[ iRightIndex ].m_iVal && iRightIndex < nGradPoints-1)
+ {
+ ++iRightIndex;
+ }
+ int iLeftIndex = iRightIndex-1;
+ int iLeftVal = rGradPoints[ iLeftIndex ].m_iVal;
+ int iRightVal = rGradPoints[ iRightIndex ].m_iVal;
+ Assert( i >= iLeftVal );
+ Assert( i <= iRightVal );
+
+ Color lColor = rGradPoints[ iLeftIndex ].m_color;
+ Color rColor = rGradPoints[ iRightIndex ].m_color;
+
+ if ( i <= iLeftVal )
+ {
+ m_colorGradient[i] = lColor;
+ }
+ else if ( i >= iRightVal )
+ {
+ m_colorGradient[i] = rColor;
+ }
+ else
+ {
+ float pct = float( i - iLeftVal ) / float( iRightVal - iLeftVal );
+ m_colorGradient[i] = LerpColor( lColor, rColor, pct );
+ }
+ }
+
+}
+
+void CustomTextureStencilGradientMapWidget::ComputeSizes()
+{
+ m_iNobSizeX = 5;
+ int sizeY = GetTall();
+
+ m_iNobSizeY = sizeY * 2 / 5;
+ m_iNobRelPosY = sizeY - m_iNobSizeY;
+
+ m_iRibbonRelPosY = 0;
+ m_iRibbonSizeY = m_iNobRelPosY - 1;
+}
+
+void CustomTextureStencilGradientMapWidget::GetColorRibbonRect( int &x1, int &y1, int &xs, int &ys )
+{
+ int controlSizeX, controlSizeY;
+ GetSize( controlSizeX, controlSizeY );
+
+ x1 = 0;
+ xs = controlSizeX;
+ y1 = m_iRibbonRelPosY;
+ ys = m_iRibbonSizeY;
+}
+
+void CustomTextureStencilGradientMapWidget::GetNobRect( int iNobIndex, int &x1, int &y1, int &xs, int &ys )
+{
+
+ Assert( iNobIndex >= 0 );
+ Assert( iNobIndex < GetNobCount() );
+
+ // Fetch x center position
+ int iNobVal = GetNobValue( iNobIndex );
+ int cx = ValToLocalX( iNobVal );
+
+ int controlSizeX, controlSizeY;
+ GetSize( controlSizeX, controlSizeY );
+
+ x1 = cx - m_iNobSizeX/2;
+ xs = m_iNobSizeX;
+ y1 = m_iNobRelPosY;
+ ys = m_iNobSizeY;
+}
+
+int CustomTextureStencilGradientMapWidget::HitTest( int x, int y, int &outOffsetX )
+{
+ int result = -1;
+ const int k_Tol = 3;
+ int bestDist = k_Tol;
+ outOffsetX = 0;
+ for ( int i = 0 ; i < GetNobCount() ; ++i )
+ {
+ int x1, y1, xs, ys;
+ GetNobRect( i, x1, y1, xs, ys );
+
+ // Reject if too far away on Y
+ if ( !m_bClickOnRanges )
+ {
+ y1 = 0;
+ ys = GetTall();
+ }
+ if ( y < y1-k_Tol ) continue;
+ if ( y > y1+ys+k_Tol) continue;
+
+ // Get horizontal error
+ int d = 0;
+ if ( x < x1 ) d = x1 - x;
+ else if ( x > x1+xs) d = x - (x1+xs);
+
+ // Closest match found so far?
+ if ( d < bestDist )
+ {
+ bestDist = d;
+ result = i;
+ outOffsetX = (x1 + xs/2) - x;
+ }
+ }
+
+ return result;
+}
+
+int CustomTextureStencilGradientMapWidget::ValToLocalX( int value, bool bClamp )
+{
+ int w = GetWide();
+ if ( bClamp )
+ {
+ if ( value < m_iMinVal ) return 0;
+ if ( value >= m_iMaxVal ) return w;
+ }
+
+ int r = m_iMaxVal - m_iMinVal;
+
+ // Don't divide by zero
+ if (r < 1 )
+ {
+ return 0;
+ }
+
+ return ( ( value - m_iMinVal ) * w + (w>>1) ) / r;
+}
+
+int CustomTextureStencilGradientMapWidget::LocalXToVal( int x, bool bClamp )
+{
+ int w = GetWide();
+
+ // Don't divide by zero
+ if (w < 1 )
+ {
+ return m_iMinVal;
+ }
+
+ if ( bClamp )
+ {
+ if ( x < 0 ) return m_iMinVal;
+ if ( x >= w ) return m_iMaxVal;
+ }
+
+ int r = m_iMaxVal - m_iMinVal;
+ return m_iMinVal + ( x * r + (r>>1) ) / w;
+}
+
+int CustomTextureStencilGradientMapWidget::GetNobValue( int nNobIndex ) const
+{
+
+ // Sentinel nob to the left?
+ if ( nNobIndex < 0 )
+ {
+ Assert( nNobIndex == -1 );
+ return m_iMinVal;
+ }
+
+ // Sentinel nob to the right?
+ if ( nNobIndex >= GetNobCount() )
+ {
+ Assert( nNobIndex == GetNobCount() );
+ return m_iMaxVal;
+ }
+
+ return m_nNobVal[ nNobIndex ];
+}
+
+void CustomTextureStencilGradientMapWidget::SetNobValue( int nNobIndex, int value )
+{
+ if ( nNobIndex < 0 || nNobIndex >= GetNobCount() )
+ {
+ Assert( nNobIndex >= 0 );
+ Assert( nNobIndex < GetNobCount() );
+ return;
+ }
+
+ // Get neighboring nob values
+ int iValLeft = GetNobValue( nNobIndex-1 );
+ int iValRight = GetNobValue( nNobIndex+1 );
+ Assert( iValLeft < iValRight );
+
+ // Subtract off the cushion
+ iValLeft += m_iNobValCushion;
+ iValRight -= m_iNobValCushion;
+
+ // No wiggle room?!?!
+ if ( iValLeft > iValRight )
+ {
+ Assert( iValLeft <= iValRight );
+
+ // Do the best we can
+ value = (iValLeft + iValRight) / 2;
+ }
+ else
+ {
+ if ( value < iValLeft )
+ {
+ value = iValLeft;
+ }
+ else if ( value > iValRight )
+ {
+ value = iValRight;
+ }
+ }
+
+ // We've clamped the value --- now slam it in place
+ SlamNobValue( nNobIndex, value );
+}
+
+void CustomTextureStencilGradientMapWidget::InitRangeCount( int nRangeCount )
+{
+ m_nRangeCount = clamp( nRangeCount, 2, k_nMaxRangeCount );
+ for ( int i = 0 ; i < GetNobCount() ; ++i )
+ {
+ SlamNobValue( i, m_iMinVal + ( i + 1 ) * ( m_iMaxVal - m_iMinVal ) / m_nRangeCount );
+ }
+}
+
+void CustomTextureStencilGradientMapWidget::AdjustRangeCount( int nRangeCount )
+{
+ nRangeCount = clamp( nRangeCount, 2, k_nMaxRangeCount );
+ Assert( m_nRangeCount >= 2 );
+
+ int oldNobCount = GetNobCount();
+ int oldRangeCount = m_nRangeCount;
+ m_nRangeCount = nRangeCount;
+ if ( m_nRangeCount < oldRangeCount )
+ {
+ // Removing ranges / nobs. Just need to space existing nobs further apart
+ //
+ // Work from back to front, so we won't
+ // conflict with the safety checks in SetNobValue
+ for ( int i = GetNobCount()-1 ; i >= 0 ; --i )
+ {
+ SetNobValue( i, m_iMinVal + ( GetNobValue( i ) - m_iMinVal ) * oldRangeCount / m_nRangeCount );
+ }
+ }
+ else if ( m_nRangeCount > oldRangeCount )
+ {
+ // Adding ranges / nobs. Compress existing nobs, and add the
+ // new once evenly at the top
+
+ // Slam new nob values to be space evenly in the space at the top
+ for ( int i = oldNobCount ; i < GetNobCount() ; ++i )
+ {
+ SlamNobValue( i, m_iMinVal + ( i + 1 ) * ( m_iMaxVal - m_iMinVal ) / m_nRangeCount );
+ }
+
+ // Work from front to back, so we won't
+ // conflict with the safety checks in SetNobValue
+ for ( int i = 0 ; i < oldNobCount ; ++i )
+ {
+ SetNobValue( i, m_iMinVal + ( GetNobValue( i ) - m_iMinVal ) * oldRangeCount / m_nRangeCount );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CustomTextureStencilGradientMapWidget::SlamNobValue( int nNobIndex, int value )
+{
+ if ( nNobIndex < 0 || nNobIndex >= GetNobCount() )
+ {
+ Assert( nNobIndex >= 0 );
+ Assert( nNobIndex < GetNobCount() );
+ return;
+ }
+
+ Assert( value >= m_iMinVal );
+ Assert( value <= m_iMaxVal );
+ m_nNobVal[ nNobIndex ] = value;
+ ComputeGradient();
+}
+
+//-----------------------------------------------------------------------------
+void CustomTextureStencilGradientMapWidget::OnCursorMoved(int x,int y)
+{
+ if( m_iDraggedNob < 0 )
+ {
+ return;
+ }
+
+ g_pVGuiInput->GetCursorPosition( x, y );
+ ScreenToLocal(x,y);
+
+ x += m_iClickOffsetX;
+ int v = LocalXToVal( x, true );
+ SetNobValue( m_iDraggedNob, v );
+
+ Repaint();
+ SendSliderMovedMessage();
+}
+
+//-----------------------------------------------------------------------------
+void CustomTextureStencilGradientMapWidget::OnMousePressed(vgui::MouseCode code)
+{
+ int x,y;
+
+ if (!IsEnabled())
+ return;
+
+ g_pVGuiInput->GetCursorPosition( x, y );
+
+ ScreenToLocal(x,y);
+ RequestFocus();
+
+ m_iDraggedNob = HitTest( x, y, m_iClickOffsetX );
+
+ if ( m_iDraggedNob >= 0 )
+ {
+ // drag the nob
+ g_pVGuiInput->SetMouseCapture(GetVPanel());
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CustomTextureStencilGradientMapWidget::OnMouseDoublePressed(vgui::MouseCode code)
+{
+ // Just handle double presses like mouse presses
+ OnMousePressed(code);
+}
+
+
+//-----------------------------------------------------------------------------
+void CustomTextureStencilGradientMapWidget::OnMouseReleased(vgui::MouseCode code)
+{
+
+ if ( m_iDraggedNob >= 0 )
+ {
+ m_iDraggedNob = -1;
+ g_pVGuiInput->SetMouseCapture(null);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: UI to select the custom image and confirm tool application
+//-----------------------------------------------------------------------------
+class CConfirmCustomizeTextureDialog : public CBaseToolUsageDialog, private ITextureRegenerator
+{
+ DECLARE_CLASS_SIMPLE( CConfirmCustomizeTextureDialog, CBaseToolUsageDialog );
+
+public:
+ CConfirmCustomizeTextureDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject );
+ virtual ~CConfirmCustomizeTextureDialog( void );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *scheme );
+ virtual void Apply( void );
+ virtual void OnCommand( const char *command );
+ virtual void OnTick( void );
+
+ void ConversionError( ConversionErrorType nError );
+
+ MESSAGE_FUNC_CHARPTR( OnFileSelected, "FileSelected", fullpath );
+ //MESSAGE_FUNC_PTR( OnRadioButtonChecked, "RadioButtonChecked", panel );
+ MESSAGE_FUNC_PTR( OnTextChanged, "TextChanged", panel ); // send by the filter combo box when it changes
+
+ MESSAGE_FUNC_PTR( OnRadioButtonChecked, "RadioButtonChecked", panel )
+ {
+ if ( eCurrentPage != ePage_SelectImage )
+ {
+ Assert( eCurrentPage == ePage_SelectImage );
+ return;
+ }
+ if ( panel == m_pUseAvatarRadioButton )
+ {
+ if ( !m_bUseAvatar )
+ {
+ UseAvatarImage();
+ }
+ }
+ else if ( panel == m_pUseAnyImageRadioButton )
+ {
+ if ( m_bUseAvatar )
+ {
+ m_imgSource.Clear();
+ m_bUseAvatar = false;
+ }
+ MarkSquareImageDirty();
+ WriteSelectImagePageControls();
+ }
+ else
+ {
+ Assert( false ); // who else is talking to us?
+ }
+ }
+
+ MESSAGE_FUNC_PTR( OnSliderMoved, "SliderMoved", panel )
+ {
+ if ( panel == m_pStencilGradientWidget )
+ {
+ MarkFilteredImageDirty();
+ }
+ else
+ {
+ // What other is talking to us?
+ Assert( false );
+ }
+ }
+
+ void OnImageUploadedToCloud( RemoteStorageFileShareResult_t *pResult, bool bIOFailure );
+
+ void CleanSquareImage()
+ {
+ if ( m_bSquareImageDirty )
+ {
+ PerformSquarize();
+ Assert( !m_bSquareImageDirty );
+ Assert( m_bFilteredImageDirty );
+ }
+ }
+
+ void CleanFilteredImage()
+ {
+ CleanSquareImage();
+ if ( m_bFilteredImageDirty )
+ {
+ PerformFilter();
+ Assert( !m_bFilteredImageDirty );
+ }
+ }
+
+ void CloseWithGenericError();
+
+private:
+
+ struct CroppedImagePanel : public CBitmapPanel {
+
+ CroppedImagePanel( CConfirmCustomizeTextureDialog *pDlg, vgui::Panel *parent )
+ : CBitmapPanel( parent, "PreviewCroppedImage" )
+ , m_pDlg(pDlg)
+ {
+ }
+
+ CConfirmCustomizeTextureDialog *m_pDlg;
+
+ void Paint()
+ {
+ m_pDlg->CleanSquareImage();
+ CBitmapPanel::Paint();
+ }
+ };
+
+ struct FilteredImagePanel : public CBitmapPanel {
+
+ FilteredImagePanel( CConfirmCustomizeTextureDialog *pDlg, vgui::Panel *parent )
+ : CBitmapPanel( parent, "PreviewFilteredImage" )
+ , m_pDlg(pDlg)
+ {
+ }
+
+ CConfirmCustomizeTextureDialog *m_pDlg;
+
+ void Paint()
+ {
+ m_pDlg->CleanFilteredImage();
+ CBitmapPanel::Paint();
+ }
+ };
+
+ vgui::FileOpenDialog *m_hImportImageDialog;
+ CBitmapPanel *m_pFilteredTextureImagePanel;
+ CBitmapPanel *m_pCroppedTextureImagePanel;
+ bool m_bFilteredImageDirty;
+ bool m_bSquareImageDirty;
+ bool m_bStencilShapeReducedImageDirty;
+ bool m_bUseAvatar;
+ bool m_bCropToSquare; // if false, we'll stretch
+ int m_nSelectedStencilPalette;
+ CUtlVector< CUtlVector< Color > > m_vecStencilPalettes;
+ CustomTextureStencilGradientMapWidget *m_pStencilGradientWidget;
+
+ enum EPage
+ {
+ ePage_SelectImage,
+ ePage_AdjustFilter,
+ ePage_FinalConfirm,
+ ePage_PerformingAction,
+
+ k_NumPages
+ };
+ EPage eCurrentPage;
+ void SetPage( EPage page );
+
+ // Page container widgets
+ vgui::EditablePanel *m_rpPagePanel[k_NumPages];
+ CItemModelPanel *m_pItemModelPanel;
+
+ Bitmap_t m_imgSource; // original resolution and aspect
+ Bitmap_t m_imgSquare; // cropped/stretched to square at submitted res
+ Bitmap_t m_imgSquareDisplay; // cropped/stretched to square at final res
+ Bitmap_t m_imgFinal; // final output res
+ Bitmap_t m_imgStencilShapeReduced;
+
+ /// Custom compositing steps defined for this item
+ CUtlVector<SDecalBlendLayer> m_vecBlendLayers;
+
+ inline bool IsSourceImageSquare() const
+ {
+ // We must know the size
+ Assert( m_imgSource.IsValid() );
+ return
+ m_imgSource.Width()*99 < m_imgSource.Height()*100
+ && m_imgSource.Height()*99 < m_imgSource.Width()*100;
+ }
+
+ ITexture *m_pCurrentPreviewedTexture;
+
+ void ActivateFileOpenDialog();
+ void PerformSquarize();
+ void PerformFilter();
+
+ vgui::ComboBox *m_pFilterCombo;
+ vgui::ComboBox *m_pSquarizeCombo;
+ vgui::ComboBox *m_pStencilModeCombo;
+ vgui::RadioButton *m_pUseAvatarRadioButton;
+ vgui::RadioButton *m_pUseAnyImageRadioButton;
+
+ enum EFilter
+ {
+ eFilter_Stencil,
+ eFilter_Identity,
+ eFilter_Painterly,
+ };
+
+ void PerformIdentityFilter();
+ void PerformStencilFilter();
+ void PerformPainterlyFilter();
+
+ // From ITextureRegenerator
+ virtual void RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect );
+ virtual void Release();
+
+ void MarkSquareImageDirty()
+ {
+ m_bSquareImageDirty = true;
+ MarkStencilShapeReducedImageDirty();
+ MarkFilteredImageDirty();
+ }
+
+ void MarkStencilShapeReducedImageDirty()
+ {
+ m_bStencilShapeReducedImageDirty = true;
+ MarkFilteredImageDirty();
+ }
+
+ void MarkFilteredImageDirty()
+ {
+ m_bFilteredImageDirty = true;
+ g_pPreviewCustomTextureDirty = true;
+ }
+
+ void ShowFilterControls();
+ void WriteSelectImagePageControls();
+ void UseAvatarImage();
+ void SelectStencilPalette( int nPalette );
+
+ // Test harness, for tweaking various values
+ #ifdef TEST_FILTERS
+ void TestFilters();
+ #endif
+};
+
+CConfirmCustomizeTextureDialog::CConfirmCustomizeTextureDialog( vgui::Panel *parent, CEconItemView *pTool, CEconItemView *pToolSubject )
+: CBaseToolUsageDialog( parent, "ConfirmCustomizeTextureDialog", pTool, pToolSubject )
+, m_hImportImageDialog( NULL )
+, m_bFilteredImageDirty(true)
+, m_bStencilShapeReducedImageDirty(true)
+, m_bSquareImageDirty(true)
+, m_bCropToSquare(false)
+, m_bUseAvatar(true)
+, m_pCurrentPreviewedTexture(NULL)
+, m_pFilterCombo(NULL)
+, m_pSquarizeCombo(NULL)
+, m_pStencilModeCombo(NULL)
+, m_pUseAvatarRadioButton(NULL)
+, m_pUseAnyImageRadioButton(NULL)
+, m_pStencilGradientWidget(NULL)
+, m_nSelectedStencilPalette(-1)
+{
+ // clear so that the preview is accurate
+ Assert( g_pPreviewCustomTexture == NULL );
+ Assert( g_pPreviewEconItem == NULL );
+ eCurrentPage = ePage_SelectImage;
+
+ m_pItemModelPanel = new CItemModelPanel( this, "paint_model" );
+ m_pItemModelPanel->SetItem( pToolSubject );
+ m_pItemModelPanel->SetActAsButton( true, false );
+
+ COMPILE_TIME_ASSERT( k_NumPages == 4 );
+ m_rpPagePanel[ePage_SelectImage] = new vgui::EditablePanel( this, "SelectImagePage" );
+ m_rpPagePanel[ePage_AdjustFilter] = new vgui::EditablePanel( this, "AdjustFilterPage" );
+ m_rpPagePanel[ePage_FinalConfirm] = new vgui::EditablePanel( this, "FinalConfirmPage" );
+ m_rpPagePanel[ePage_PerformingAction] = new vgui::EditablePanel( this, "PerformingActionPage" );
+
+ vgui::EditablePanel *pSelectImagePreviewGroupBox = new vgui::EditablePanel( m_rpPagePanel[ePage_SelectImage], "PreviewImageGroupBox" );
+ m_pCroppedTextureImagePanel = new CroppedImagePanel( this, pSelectImagePreviewGroupBox );
+
+ vgui::EditablePanel *pAdjustFilterPreviewGroupBox = new vgui::EditablePanel( m_rpPagePanel[ePage_AdjustFilter], "PreviewImageGroupBox" );
+ m_pFilteredTextureImagePanel = new FilteredImagePanel( this, pAdjustFilterPreviewGroupBox );
+
+ //
+ // Locate / create the procedoral material & texture to show the
+ // results of the filtered texture
+ //
+
+ ITexture *pPreviewTexture = NULL;
+ if ( g_pMaterialSystem->IsTextureLoaded( k_rchCustomTextureFilterPreviewTextureName ) )
+ {
+ pPreviewTexture = g_pMaterialSystem->FindTexture( k_rchCustomTextureFilterPreviewTextureName, TEXTURE_GROUP_VGUI );
+ pPreviewTexture->AddRef();
+ Assert( pPreviewTexture );
+ }
+ else
+ {
+ pPreviewTexture = g_pMaterialSystem->CreateProceduralTexture(
+ k_rchCustomTextureFilterPreviewTextureName,
+ TEXTURE_GROUP_VGUI,
+ k_nCustomImageSize, k_nCustomImageSize,
+ IMAGE_FORMAT_RGBA8888,
+ TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT | TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_NOLOD
+ );
+ Assert( pPreviewTexture );
+ }
+ pPreviewTexture->SetTextureRegenerator( this ); // note carefully order of operations here. See Release()
+ g_pPreviewCustomTexture = pPreviewTexture;
+ g_pPreviewCustomTextureDirty = true;
+ g_pPreviewEconItem = m_pItemModelPanel->GetItem();
+
+ vgui::ivgui()->AddTickSignal( GetVPanel(), 0 );
+
+ // Parse blend operations from the tool definition KV
+ KeyValues *pkvBlendLayers = pToolSubject->GetDefinitionKey( "custom_texture_blend_steps" );
+ if ( pkvBlendLayers )
+ {
+ for ( KeyValues *kvLayer = pkvBlendLayers->GetFirstTrueSubKey() ; kvLayer ; kvLayer = kvLayer->GetNextTrueSubKey() )
+ {
+ int idx = m_vecBlendLayers.AddToTail();
+ CUtlString sErrMsg;
+ if ( !m_vecBlendLayers[idx].FromKV( kvLayer, sErrMsg ) )
+ {
+ Warning( "Bogus custom texture blend layer definition '%s'. %s\n", kvLayer->GetName(), (const char *)sErrMsg );
+ Assert( !"Bogus custom texture blend layer!" );
+ m_vecBlendLayers.Remove( idx );
+ }
+ }
+ }
+
+ // Setup stencil palettes
+
+ CUtlVector<Color> *pPalette;
+ pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ];
+ pPalette->AddToTail( Color( 54, 38, 0, 255 ) );
+ pPalette->AddToTail( Color( 236, 236, 217, 255 ) );
+
+ pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ];
+ pPalette->AddToTail( Color( 54, 38, 0, 255 ) );
+ pPalette->AddToTail( Color( 137, 131, 116, 255 ) );
+ pPalette->AddToTail( Color( 236, 236, 217, 255 ) );
+ pPalette->AddToTail( Color( 254, 255, 228, 255 ) );
+
+ pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ];
+ pPalette->AddToTail( Color( 186, 80, 34, 255 ) );
+ pPalette->AddToTail( Color( 243, 231, 194, 255 ) );
+
+ pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ];
+ pPalette->AddToTail( Color( 186, 80, 34, 255 ) );
+ pPalette->AddToTail( Color( 217, 162, 121, 255 ) );
+ pPalette->AddToTail( Color( 243, 231, 194, 255 ) );
+ pPalette->AddToTail( Color( 255, 247, 220, 255 ) );
+
+ pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ];
+ pPalette->AddToTail( Color( 101, 72, 54, 255 ) );
+ pPalette->AddToTail( Color( 229, 150, 73, 255 ) );
+
+ pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ];
+ pPalette->AddToTail( Color( 101, 72, 54, 255 ) );
+ pPalette->AddToTail( Color( 161, 100, 47, 255 ) );
+ pPalette->AddToTail( Color( 229, 150, 73, 255 ) );
+ pPalette->AddToTail( Color( 255, 207, 154, 255 ) );
+
+ pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ];
+ pPalette->AddToTail( Color( 88, 84, 80, 255 ) );
+ pPalette->AddToTail( Color( 160, 84, 72, 255 ) );
+ pPalette->AddToTail( Color( 216, 212, 192, 255 ) );
+
+ pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ];
+ pPalette->AddToTail( Color( 54, 38, 0, 255 ) );
+ pPalette->AddToTail( Color( 163, 110, 0, 255 ) );
+ pPalette->AddToTail( Color( 215, 171, 2, 255 ) );
+ pPalette->AddToTail( Color( 197, 192, 171, 255 ) );
+
+ // !TEST! Import the palettes from an image
+ #if 0
+ {
+ m_vecStencilPalettes.RemoveAll();
+ Bitmap_t imgPal;
+ Assert( ImgUtl_LoadBitmap( "d:/decal_tool_palettes_bay.png", imgPal ) == CE_SUCCESS );
+ const int kSwatchSz = 10;
+ for (int y = kSwatchSz/2 ; y < imgPal.Height() ; y += kSwatchSz )
+ {
+ CUtlVector<Color> palette;
+ for (int x = kSwatchSz/2 ; x < imgPal.Width() ; x += kSwatchSz )
+ {
+ palette.AddToTail( imgPal.GetColor( x, y ) );
+ }
+
+ // Strip off solid white entries from the end. (If these are in the palette,
+ // they have to come first!)
+ while ( palette.Count() > 0 && ApproxColorDistSq( palette[palette.Count()-1], Color(255,255,255,255) ) < 12 )
+ {
+ palette.Remove( palette.Count()-1 );
+ }
+ Assert( palette.Count() != 1 ); // only a single entry in the palette? Should be 0, or at least 2
+ if ( palette.Count() > 1 )
+ {
+ // Reverse the palette, so it is ordered dark -> light.
+ for ( int l = 0, r = palette.Count()-1 ; l < r ; ++l, --r )
+ {
+ Color t = palette[l];
+ palette[l] = palette[r];
+ palette[r] = t;
+ }
+
+ CUtlVector<Color> *pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ];
+ Msg( "pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ];\n" );
+ for (int j = 0 ; j < palette.Count() ; ++j )
+ {
+ pPalette->AddToTail( palette[j] );
+ Msg( "pPalette->AddToTail( Color( %d, %d, %d, 255 ) );\n", palette[j].r(), palette[j].g(), palette[j].b() );
+ }
+ Msg( "\n" );
+ }
+ }
+ }
+ #endif
+
+}
+
+CConfirmCustomizeTextureDialog::~CConfirmCustomizeTextureDialog( void )
+{
+
+ // Clean up filtered texture
+ Release();
+
+ delete m_hImportImageDialog;
+ m_hImportImageDialog = NULL;
+}
+
+void CConfirmCustomizeTextureDialog::SetPage( EPage page )
+{
+ eCurrentPage = page;
+ switch ( eCurrentPage )
+ {
+ default:
+ Assert(false);
+ eCurrentPage = ePage_SelectImage;
+ case ePage_SelectImage:
+ WriteSelectImagePageControls();
+ break;
+
+ case ePage_AdjustFilter:
+ // Make sure proper controls are shown
+ ShowFilterControls();
+ break;
+
+ case ePage_FinalConfirm:
+ break;
+
+ case ePage_PerformingAction:
+ break;
+ }
+
+ // !KLUDGE! We need to hide ourselves while the file open dialog is up
+ //SetVisible( eCurrentPage != ePage_SelectImage );
+
+ for ( int i = 0 ; i < k_NumPages ; ++i )
+ {
+ if ( m_rpPagePanel[i] )
+ {
+ m_rpPagePanel[i]->SetVisible( i == eCurrentPage );
+ }
+ }
+
+ vgui::EditablePanel *pPreviewProupPanel = NULL;
+ if ( m_rpPagePanel[eCurrentPage] )
+ {
+ pPreviewProupPanel = dynamic_cast<vgui::EditablePanel *>( m_rpPagePanel[eCurrentPage]->FindChildByName( "PreviewModelGroupBox" ) );
+ }
+ if ( pPreviewProupPanel )
+ {
+ m_pItemModelPanel->SetVisible( true );
+ m_pItemModelPanel->SetParent( pPreviewProupPanel );
+ m_pItemModelPanel->SetPos( 10, 10 );
+ m_pItemModelPanel->SetSize( pPreviewProupPanel->GetWide() - 20, pPreviewProupPanel->GetTall() - 20 );
+ m_pItemModelPanel->UpdatePanels();
+ }
+ else
+ {
+ m_pItemModelPanel->SetVisible( false );
+ }
+}
+
+void CConfirmCustomizeTextureDialog::ActivateFileOpenDialog()
+{
+ // Create the dialog the first time it's used
+ if (m_hImportImageDialog == NULL)
+ {
+ m_hImportImageDialog = new vgui::FileOpenDialog( NULL, "#ToolCustomizeTextureBrowseDialogTitle", true );
+#ifdef WIN32
+ m_hImportImageDialog->AddFilter( "*.tga,*.jpg,*.png,*.bmp", "#GameUI_All_Images", true );
+#else
+ m_hImportImageDialog->AddFilter( "*.tga,*.jpg,*.png", "#GameUI_All_Images", true );
+#endif
+ m_hImportImageDialog->AddFilter( "*.tga", "#GameUI_TGA_Images", false );
+ m_hImportImageDialog->AddFilter( "*.jpg", "#GameUI_JPEG_Images", false );
+ m_hImportImageDialog->AddFilter( "*.png", "#GameUI_PNG_Images", false );
+#ifdef WIN32
+ m_hImportImageDialog->AddFilter( "*.bmp", "#GameUI_BMP_Images", false );
+#endif
+ m_hImportImageDialog->AddActionSignalTarget( this );
+ }
+
+ // Activate it
+ m_hImportImageDialog->DoModal( false );
+ m_hImportImageDialog->Activate();
+}
+
+void CConfirmCustomizeTextureDialog::OnCommand( const char *command )
+{
+ if (!stricmp( command, "pick_image" ) )
+ {
+ ActivateFileOpenDialog();
+ return;
+ }
+
+ // !KLUDGE! Base class closes window. I don't want to do this.
+ if ( !Q_stricmp( command, "apply" ) )
+ {
+ Apply();
+ return;
+ }
+
+ if ( !Q_stricmp( command, "next_page" ) )
+ {
+ if ( eCurrentPage < ePage_FinalConfirm )
+ {
+ SetPage( (EPage)(eCurrentPage + 1) );
+ }
+ return;
+ }
+
+ if ( !Q_stricmp( command, "prev_page" ) )
+ {
+ if ( eCurrentPage > ePage_SelectImage )
+ {
+ SetPage( (EPage)(eCurrentPage - 1) );
+ }
+ return;
+ }
+
+ if ( !Q_stricmp( command, "next_stencil_palette" ) )
+ {
+ SelectStencilPalette( m_nSelectedStencilPalette + 1 );
+ return;
+ }
+
+ if ( !Q_stricmp( command, "prev_stencil_palette" ) )
+ {
+ SelectStencilPalette( m_nSelectedStencilPalette + m_vecStencilPalettes.Count() - 1 );
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+void CConfirmCustomizeTextureDialog::SelectStencilPalette( int nPalette )
+{
+ while ( nPalette < 0 )
+ {
+ nPalette += m_vecStencilPalettes.Count();
+ }
+ m_nSelectedStencilPalette = nPalette % m_vecStencilPalettes.Count();
+ MarkFilteredImageDirty();
+
+ if ( m_pStencilGradientWidget )
+ {
+ const CUtlVector<Color> &pal = m_vecStencilPalettes[m_nSelectedStencilPalette];
+ m_pStencilGradientWidget->InitRangeCount( pal.Count() );
+ m_pStencilGradientWidget->SetRangeColors( pal.Base() );
+ }
+}
+
+
+static void ListenToControlsRecursive( vgui::Panel *pPanel, vgui::Panel *pListener )
+{
+ if ( pPanel == NULL )
+ {
+ return;
+ }
+ if (
+ dynamic_cast<vgui::Button *>( pPanel )
+ || dynamic_cast<vgui::Slider *>( pPanel )
+ || dynamic_cast<vgui::ComboBox *>( pPanel )
+ || dynamic_cast<CustomTextureStencilGradientMapWidget *>( pPanel )
+ )
+ {
+ pPanel->AddActionSignalTarget( pListener );
+ }
+ else
+ {
+ for ( int i = 0 ; i < pPanel->GetChildCount() ; ++i )
+ {
+ ListenToControlsRecursive( pPanel->GetChild(i), pListener );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmCustomizeTextureDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ LoadControlSettings( "Resource/UI/econ/ConfirmCustomizeTextureDialog.res" );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_pFilterCombo = dynamic_cast<vgui::ComboBox *>( FindChildByName("FilterComboBox", true ) );
+ Assert( m_pFilterCombo );
+ if ( m_pFilterCombo )
+ {
+ m_pFilterCombo->RemoveAll();
+
+ COMPILE_TIME_ASSERT( eFilter_Stencil == 0 );
+ m_pFilterCombo->AddItem( "#ToolCustomizeTextureFilterStencil", NULL );
+
+ //COMPILE_TIME_ASSERT( eFilter_Painterly == 0 );
+ //m_pFilterCombo->AddItem( "#ToolCustomizeTextureFilterPainterly", NULL );
+
+ COMPILE_TIME_ASSERT( eFilter_Identity == 1 );
+ #ifdef _DEBUG
+ m_pFilterCombo->AddItem( "None", NULL );
+ #endif
+
+ //m_pFilterCombo->SilentActivateItemByRow( eFilter_Painterly );
+ m_pFilterCombo->SilentActivateItemByRow( eFilter_Stencil );
+ }
+
+ m_pSquarizeCombo = dynamic_cast<vgui::ComboBox *>( FindChildByName("SquarizeComboBox", true ) );
+ Assert( m_pSquarizeCombo );
+
+ m_pStencilModeCombo = dynamic_cast<vgui::ComboBox *>( FindChildByName("StencilModeComboBox", true ) );
+ Assert( m_pStencilModeCombo );
+ if ( m_pStencilModeCombo )
+ {
+ m_pStencilModeCombo->AddItem( "#ToolCustomizeTextureStencilMatchByIntensity", NULL );
+ m_pStencilModeCombo->AddItem( "#ToolCustomizeTextureStencilMatchByColor", NULL );
+ m_pStencilModeCombo->SilentActivateItemByRow( 0 );
+ }
+
+ m_pUseAvatarRadioButton = dynamic_cast<vgui::RadioButton *>( FindChildByName("UseAvatarRadio", true ) );
+ m_pUseAnyImageRadioButton = dynamic_cast<vgui::RadioButton *>( FindChildByName("UseAnyimageRadio", true ) );
+
+ m_pStencilGradientWidget = dynamic_cast<CustomTextureStencilGradientMapWidget *>( FindChildByName("StencilGradientMap", true ) );
+ Assert( m_pStencilGradientWidget );
+
+ for ( int i = 0 ; i < k_NumPages ; ++i )
+ {
+ ListenToControlsRecursive( m_rpPagePanel[i], this );
+ }
+
+ UseAvatarImage();
+
+ SetPage( ePage_SelectImage );
+
+ // Flip this flag to activate the test harness
+ #ifdef TEST_FILTERS
+ TestFilters();
+ #endif
+
+ SelectStencilPalette( 0 );
+}
+
+void CConfirmCustomizeTextureDialog::UseAvatarImage()
+{
+ // assume failure
+ m_imgSource.Clear();
+ m_bUseAvatar = false;
+
+ if ( steamapicontext && steamapicontext->SteamUser() )
+ {
+
+ const int k_nAvatarImageSize = 184;
+ m_imgSource.Init( k_nAvatarImageSize, k_nAvatarImageSize, IMAGE_FORMAT_RGBA8888 );
+ int iAvatar = steamapicontext->SteamFriends()->GetLargeFriendAvatar( steamapicontext->SteamUser()->GetSteamID() );
+ if ( !steamapicontext->SteamUtils()->GetImageRGBA( iAvatar, m_imgSource.GetBits(), k_nAvatarImageSize*k_nAvatarImageSize*4 ) )
+ {
+ m_imgSource.Clear();
+ }
+ else
+ {
+ m_bUseAvatar = true;
+ }
+ }
+
+ WriteSelectImagePageControls();
+ MarkSquareImageDirty();
+}
+
+void CConfirmCustomizeTextureDialog::WriteSelectImagePageControls()
+{
+ if ( !m_pSquarizeCombo )
+ {
+ return;
+ }
+
+ m_pSquarizeCombo->RemoveAll();
+
+ CExButton *pNextButton = dynamic_cast<CExButton *>( m_rpPagePanel[ePage_SelectImage]->FindChildByName( "NextButton", true ) );
+ if ( !pNextButton )
+ {
+ return;
+ }
+
+ if ( m_pUseAvatarRadioButton )
+ {
+ if ( !m_pUseAvatarRadioButton->IsSelected() )
+ {
+ m_pUseAvatarRadioButton->SetSelected( m_bUseAvatar );
+ }
+ }
+ if ( m_pUseAnyImageRadioButton )
+ {
+ if ( !m_pUseAnyImageRadioButton->IsSelected() )
+ {
+ m_pUseAnyImageRadioButton->SetSelected( !m_bUseAvatar );
+ }
+ }
+
+ if ( !m_imgSource.IsValid() )
+ {
+ // No image yet selected
+ m_pSquarizeCombo->SetVisible( false );
+ pNextButton->SetEnabled( false );
+ m_pCroppedTextureImagePanel->SetVisible( false );
+ return;
+ }
+ m_pCroppedTextureImagePanel->SetVisible( true );
+ pNextButton->SetEnabled( true );
+
+ // Nearly square already?
+ if ( IsSourceImageSquare() )
+ {
+ // Nearly square. No need to offer any options
+ m_pSquarizeCombo->SetVisible( false );
+ }
+ else
+ {
+ m_pSquarizeCombo->AddItem( "#ToolCustomizeTextureStretch", NULL );
+ m_pSquarizeCombo->AddItem( "#ToolCustomizeTextureCrop", NULL );
+ m_pSquarizeCombo->SetVisible( true );
+ m_pSquarizeCombo->ActivateItemByRow( m_bCropToSquare ? 1 : 0 );
+ }
+
+
+}
+
+void CConfirmCustomizeTextureDialog::OnTick( void )
+{
+ BaseClass::OnTick();
+
+ // Process, depending on currently selected page
+ switch ( eCurrentPage )
+ {
+ default:
+ Assert(false);
+ eCurrentPage = ePage_SelectImage;
+ case ePage_SelectImage:
+ break;
+
+ case ePage_AdjustFilter:
+ break;
+
+ case ePage_FinalConfirm:
+ break;
+
+ case ePage_PerformingAction:
+ break;
+ }
+}
+
+void CConfirmCustomizeTextureDialog::ShowFilterControls()
+{
+ EFilter f = (EFilter)m_pFilterCombo->GetActiveItem();
+
+ vgui::Panel *p;
+
+ p = m_rpPagePanel[ePage_AdjustFilter]->FindChildByName( "PainterlyOptions", true );
+ if ( p )
+ {
+ p->SetVisible( f == eFilter_Painterly );
+ }
+
+ p = m_rpPagePanel[ePage_AdjustFilter]->FindChildByName( "StencilOptions", true );
+ if ( p )
+ {
+ p->SetVisible( f == eFilter_Stencil );
+ }
+}
+
+void CConfirmCustomizeTextureDialog::PerformSquarize()
+{
+ if ( m_bCropToSquare && !IsSourceImageSquare() )
+ {
+ // Select the smaller dimension as the size
+ int nSize = MIN( m_imgSource.Width(), m_imgSource.Height() );
+
+ // Crop it.
+ // Yeah, the crop and resize could be done all in one step.
+ // And...I don't care.
+ int x0 = ( m_imgSource.Width() - nSize ) / 2;
+ int y0 = ( m_imgSource.Height() - nSize ) / 2;
+ m_imgSquare.Crop( x0, y0, nSize, nSize, &m_imgSource );
+ }
+ else
+ {
+ m_imgSquare.MakeLogicalCopyOf( m_imgSource );
+ }
+
+ // Reduce it for display purposes
+ ImgUtl_ResizeBitmap( m_imgSquareDisplay, k_nCustomImageSize, k_nCustomImageSize, &m_imgSquare );
+
+ // Square image is now up-to-date with options
+ m_bSquareImageDirty = false;
+
+ if ( m_pCroppedTextureImagePanel != NULL )
+ {
+ m_pCroppedTextureImagePanel->SetBitmap( m_imgSquareDisplay );
+ }
+
+ // We need to re-run our filter anytime this changes
+ MarkFilteredImageDirty();
+}
+
+void CConfirmCustomizeTextureDialog::PerformFilter()
+{
+
+ // this can take a while, put up a waiting cursor
+ vgui::surface()->SetCursor( vgui::dc_hourglass );
+
+ switch ( (EFilter)m_pFilterCombo->GetActiveItem() )
+ {
+ case eFilter_Identity:
+ // !FIXME! Only allow while in dev universe?
+ PerformIdentityFilter();
+ break;
+ default:
+ Assert( false );
+ case eFilter_Stencil:
+ PerformStencilFilter();
+ break;
+ case eFilter_Painterly:
+ PerformPainterlyFilter();
+ break;
+ }
+
+ // Now apply the blend layers
+ static bool bDoBlendLayers = true;
+ if ( bDoBlendLayers )
+ {
+ for ( int i = 0; i < m_vecBlendLayers.Size() ; ++i )
+ {
+ m_vecBlendLayers[i].Apply( m_imgFinal );
+ }
+ }
+
+ // And the texture on the 3D model
+ g_pPreviewCustomTextureDirty = true;
+
+ m_bFilteredImageDirty = false;
+
+ if ( m_pFilteredTextureImagePanel != NULL )
+ {
+ m_pFilteredTextureImagePanel->SetBitmap( m_imgFinal );
+ }
+
+ // change the cursor back to normal
+ vgui::surface()->SetCursor( vgui::dc_user );
+}
+
+void CConfirmCustomizeTextureDialog::PerformIdentityFilter()
+{
+ ImgUtl_ResizeBitmap( m_imgFinal, k_nCustomImageSize, k_nCustomImageSize, &m_imgSquare );
+}
+
+void CConfirmCustomizeTextureDialog::PerformStencilFilter()
+{
+
+ // Check if the shape reduced image is dirty
+ if ( m_bStencilShapeReducedImageDirty )
+ {
+ Bitmap_t imgTemp1, imgTemp2;
+
+// Need a slider to control this. Works OK for color match, poorly for intensity.
+// Best for all cases is to just do nothing
+// // Downsample FIRST to 2X res
+// ImgUtl_ResizeBitmap( imgTemp1, k_nCustomImageSize*2, k_nCustomImageSize*2, &m_imgSquare );
+//
+// // Run the bilateral filter several times
+// static float thresh1 = .7f; static int rad1 = 1; static float amount1 = 1.0f;
+// static float thresh2 = .8f; static int rad2 = 1; static float amount2 = 1.0f;
+// static float thresh3 = .9f; static int rad3 = 2; static float amount3 = 1.0f;
+// Bitmap_t t;
+// BilateralFilter( imgTemp1, imgTemp2, rad1, thresh1, amount1 );
+// static int rounds = 4;
+// for ( int r = 0 ; r < rounds ; ++r )
+// {
+// BilateralFilter( imgTemp2, imgTemp1, rad2, thresh2, amount2 );
+// BilateralFilter( imgTemp1, imgTemp2, rad2, thresh2, amount2 );
+// }
+// //BilateralFilter( imgTemp2, m_imgFinal, rad3, thresh3, amount3 );
+// BilateralFilter( imgTemp2, m_imgStencilShapeReduced, rad3, thresh3, amount3 );
+
+ // Downsample FIRST to 2X res
+ ImgUtl_ResizeBitmap( m_imgStencilShapeReduced, k_nCustomImageSize*2, k_nCustomImageSize*2, &m_imgSquare );
+
+ m_bStencilShapeReducedImageDirty = false;
+ }
+
+ // Color matching
+ {
+// Color swatches[] =
+// {
+// Color( 255, 255, 255 ),
+// Color( 183, 224, 252 ), // sky light
+// Color( 83, 109, 205 ), // sky med
+// Color( 64, 68, 195 ), // sky dark
+// Color( 100, 68, 57 ), // skin demo
+// Color( 139, 101, 84 ), // skin demo light
+// Color( 133, 105, 68 ), // saxton hair
+// Color( 252, 169, 131 ), // skin light
+// Color( 194, 132, 106 ), // skin
+//
+// //Color( 255, 255, 255 ),
+// //Color( 246, 231, 222 ),
+// //Color( 218, 189, 171 ),
+// //Color( 193, 161, 138 ),
+// //
+// //Color( 248, 185, 138 ),
+// //Color( 245, 173, 135 ),
+// //Color( 239, 152, 73 ),
+// //Color( 241, 129, 73 ),
+// //
+// //Color( 106, 69, 52 ),
+// //Color( 145, 58, 31 ),
+// //Color( 189, 58, 58 ),
+// //Color( 157, 48, 47 ),
+// //Color( 69, 44, 37 ),
+// //
+// //Color( 107, 106, 101 ),
+// //Color( 118, 138, 136 ),
+// //Color( 91, 122, 140 ),
+// //Color( 56, 92, 120 ),
+// //Color( 52, 47, 44 ),
+// };
+
+ Bitmap_t imgTemp1;
+
+ static float colorReplacePct = 1.0f;
+
+ // match by color, or intensity?
+ if ( m_pStencilModeCombo && m_pStencilModeCombo->GetActiveItem() == 0 && m_pStencilGradientWidget )
+ {
+ imgTemp1.Init( m_imgStencilShapeReduced.Width(), m_imgStencilShapeReduced.Height(), IMAGE_FORMAT_RGBA8888 );
+ for ( int y = 0 ; y < imgTemp1.Height() ; ++y )
+ {
+ for ( int x = 0 ; x < imgTemp1.Width() ; ++x )
+ {
+ Color c = m_imgStencilShapeReduced.GetColor( x, y );
+ Vector lab = TextureToLab( c );
+ int index = clamp(lab.x * (255.0f/100.0f) + .5f, 0.0, 255.0f);
+ imgTemp1.SetColor( x, y, m_pStencilGradientWidget->m_colorGradient[ index ] );
+ }
+ }
+ }
+ else
+ {
+
+ Assert( m_nSelectedStencilPalette >= 0 );
+ Assert( m_nSelectedStencilPalette < m_vecStencilPalettes.Count() );
+ const CUtlVector<Color> &pal = m_vecStencilPalettes[ m_nSelectedStencilPalette ];
+
+ // Determine "weight" of each swatch, from the relative sizes of the
+ // gradient widget ranges
+ CUtlVector<float> vecSwatchWeight;
+ for ( int i = 0 ; i < pal.Size() ; ++i )
+ {
+ float weight = 1.0f;
+ if ( m_pStencilGradientWidget )
+ {
+ weight = float( m_pStencilGradientWidget->GetNobValue(i) - m_pStencilGradientWidget->GetNobValue(i - 1) );
+ }
+ vecSwatchWeight.AddToTail( weight );
+
+ }
+
+ ColorReplace( m_imgStencilShapeReduced, imgTemp1, pal.Count(), pal.Base(), colorReplacePct, vecSwatchWeight.Base() );
+ }
+
+ // Now downsample to the final size
+ ImgUtl_ResizeBitmap( m_imgFinal, k_nCustomImageSize, k_nCustomImageSize, &imgTemp1 );\
+ }
+
+// // !KLUDGE!
+// if ( m_pStencilGradientWidget == NULL )
+// {
+// PerformIdentityFilter();
+// return;
+// }
+//
+// // Make sure temp image is properly allocated
+// imgTemp1.Init( m_imgSquare.Width(), m_imgSquare.Height(), IMAGE_FORMAT_RGBA8888 );
+//
+// // Perform stencil operation
+// for ( int y = 0 ; y < m_imgSquare.Height() ; ++y )
+// {
+// for ( int x = 0 ; x < m_imgSquare.Height() ; ++x )
+// {
+// Color c = m_imgSquare.GetColor(x,y);
+//
+// // Compute "value" using simple average. (No visual
+// // weighting for this.)
+// int v = ( (int)c.r() + (int)c.g() + (int)c.g() ) / 3;
+//
+// // Apply gradient map
+// Color result = m_pStencilGradientWidget->m_colorGradient[ v ];
+// imgTemp1.SetColor( x, y, result );
+// }
+// }
+//
+// // Now downsample to the final size
+// ImgUtl_ResizeBitmap( m_imgFinal, k_nCustomImageSize, k_nCustomImageSize, &m_imgTemp );
+}
+
+const int k_BrushStrokeSize = 64;
+static byte s_bBrushStrokeData[k_BrushStrokeSize][k_BrushStrokeSize] =
+{
+ { 0x8C, 0x86, 0x87, 0x87, 0x86, 0x88, 0x88, 0x87, 0x86, 0x88, 0x8F, 0x8E, 0x8C, 0x8E, 0x8D, 0x8E, 0x8B, 0x8A, 0x8A, 0x94, 0xAE, 0xB6, 0xB5, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB3, 0xB0, 0xB0, 0xB2, 0x9E, 0x9A, 0x9C, 0x9C, 0x9A, 0x99, 0x97, 0x98, 0x96, 0x9A, 0x9D, 0x9F, 0x9E, 0x9D, 0x9E, 0x9F, 0x9B, 0x9A, 0x99, 0x98, 0x95, 0x91, 0x8F, 0x8F, 0x8E, 0x89, 0x88, 0x89, 0x88, 0x85, 0x87, 0x8B },
+ { 0x85, 0x7C, 0x7D, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7D, 0x7F, 0x87, 0x88, 0x88, 0x89, 0x87, 0x86, 0x86, 0x83, 0x81, 0x8B, 0xA9, 0xB2, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xAF, 0xAD, 0xAD, 0xAE, 0x97, 0x92, 0x94, 0x94, 0x93, 0x92, 0x91, 0x92, 0x91, 0x94, 0x98, 0x9B, 0x9A, 0x99, 0x99, 0x99, 0x96, 0x95, 0x96, 0x98, 0x98, 0x98, 0x99, 0x9A, 0x93, 0x86, 0x84, 0x84, 0x80, 0x7D, 0x7D, 0x83 },
+ { 0x86, 0x7D, 0x7E, 0x81, 0x80, 0x7F, 0x7F, 0x82, 0x83, 0x84, 0x8A, 0x89, 0x86, 0x87, 0x88, 0x89, 0x86, 0x87, 0x85, 0x8E, 0xAA, 0xB2, 0xB0, 0xB1, 0xB0, 0xB1, 0xB1, 0xB1, 0xB0, 0xAE, 0xAD, 0xAD, 0x9B, 0x96, 0x96, 0x96, 0x94, 0x95, 0x94, 0x96, 0x97, 0x99, 0x9C, 0x9E, 0x9D, 0x9D, 0x9C, 0x9C, 0x9A, 0x99, 0x99, 0x99, 0x9A, 0x9B, 0x9D, 0x9E, 0x9B, 0x86, 0x85, 0x86, 0x83, 0x80, 0x7F, 0x88 },
+ { 0x84, 0x7D, 0x7F, 0x81, 0x7F, 0x80, 0x83, 0x88, 0x88, 0x88, 0x8B, 0x8A, 0x88, 0x89, 0x89, 0x89, 0x84, 0x85, 0x85, 0x8E, 0xAB, 0xB4, 0xB2, 0xB3, 0xB2, 0xB2, 0xB3, 0xB3, 0xB2, 0xB0, 0xAF, 0xAE, 0x99, 0x94, 0x94, 0x94, 0x93, 0x96, 0x97, 0x99, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9C, 0x99, 0x99, 0x99, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9D, 0x85, 0x85, 0x87, 0x84, 0x82, 0x7E, 0x87 },
+ { 0x85, 0x7D, 0x7E, 0x7E, 0x7F, 0x84, 0x88, 0x8A, 0x8A, 0x88, 0x88, 0x89, 0x87, 0x87, 0x89, 0x88, 0x89, 0x88, 0x86, 0x90, 0xAA, 0xB4, 0xB3, 0xB1, 0xB1, 0xB2, 0xB3, 0xB3, 0xB2, 0xB1, 0xAF, 0xAE, 0x97, 0x93, 0x94, 0x94, 0x95, 0x99, 0x9C, 0x9E, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x97, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9C, 0x9C, 0x86, 0x86, 0x86, 0x85, 0x85, 0x7F, 0x86 },
+ { 0x85, 0x7A, 0x76, 0x75, 0x77, 0x7D, 0x7D, 0x7A, 0x7B, 0x7D, 0x7D, 0x7D, 0x7A, 0x7F, 0x8E, 0x97, 0x90, 0x94, 0x9A, 0xA0, 0xAF, 0xB2, 0xB2, 0xB2, 0xB1, 0xB2, 0xB3, 0xB3, 0xB3, 0xB2, 0xB0, 0xAF, 0x96, 0x92, 0x94, 0x93, 0x93, 0x97, 0x99, 0x9A, 0x9A, 0x9B, 0x9C, 0x9E, 0x9E, 0x9D, 0x9C, 0x9C, 0x9A, 0x9A, 0x9B, 0x9C, 0x9E, 0x9F, 0x9F, 0x9E, 0x9B, 0x8A, 0x87, 0x87, 0x86, 0x85, 0x80, 0x86 },
+ { 0x89, 0x7B, 0x75, 0x74, 0x75, 0x77, 0x75, 0x73, 0x75, 0x77, 0x79, 0x7B, 0x7C, 0x82, 0x92, 0x9C, 0x97, 0x9F, 0xAB, 0xAE, 0xB1, 0xB1, 0xB4, 0xB4, 0xB3, 0xB4, 0xB5, 0xB5, 0xB4, 0xB5, 0xB3, 0xB2, 0x9A, 0x97, 0x99, 0x98, 0x97, 0x99, 0x98, 0x98, 0x9A, 0x9B, 0x9E, 0x9F, 0x9F, 0x9F, 0x9E, 0x9D, 0x9A, 0x9B, 0x9C, 0x9D, 0xA1, 0xA5, 0xA7, 0xA6, 0xA7, 0x9A, 0x94, 0x94, 0x93, 0x8A, 0x83, 0x89 },
+ { 0x8B, 0x7E, 0x7A, 0x7B, 0x79, 0x79, 0x78, 0x79, 0x75, 0x77, 0x7E, 0x90, 0xA0, 0xA8, 0xAC, 0xA7, 0xA9, 0xAA, 0xB0, 0xB0, 0xB1, 0xB1, 0xB4, 0xB2, 0xB3, 0xB5, 0xB6, 0xB5, 0xB5, 0xB6, 0xB5, 0xB3, 0x9B, 0x99, 0x9D, 0x9D, 0x9C, 0x9E, 0x9C, 0x9B, 0x9C, 0x9D, 0x9F, 0xA0, 0x9F, 0x9F, 0x9E, 0x9F, 0x9A, 0x9B, 0x9B, 0x9C, 0xA0, 0xA5, 0xA7, 0xA8, 0xA9, 0xA0, 0x9A, 0x9C, 0x9B, 0x8D, 0x85, 0x8B },
+ { 0x8A, 0x7E, 0x7B, 0x77, 0x79, 0x78, 0x77, 0x74, 0x75, 0x70, 0x87, 0xAC, 0xAF, 0xAF, 0xB1, 0xB1, 0xAE, 0xAD, 0xB1, 0xB0, 0xB1, 0xB0, 0xB5, 0xB5, 0xB5, 0xB4, 0xB5, 0xB5, 0xB4, 0xB5, 0xB4, 0xB1, 0x9A, 0x9B, 0x9C, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9E, 0x9F, 0x9F, 0x9F, 0x9F, 0x9E, 0x9A, 0x9A, 0x9C, 0x9D, 0xA0, 0xA5, 0xA7, 0xA9, 0xA8, 0xA0, 0x9B, 0x9A, 0x99, 0x8D, 0x83, 0x8B },
+ { 0x8B, 0x7D, 0x7A, 0x77, 0x78, 0x77, 0x78, 0x76, 0x77, 0x73, 0x88, 0xA8, 0xAC, 0xAE, 0xB2, 0xB3, 0xB2, 0xB1, 0xB1, 0xB0, 0xB5, 0xB4, 0xB5, 0xB4, 0xB6, 0xB5, 0xB6, 0xB6, 0xB5, 0xB5, 0xB5, 0xB2, 0x9A, 0x9B, 0x9C, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9E, 0x9D, 0x9E, 0x9E, 0xA2, 0xA7, 0xA7, 0xA7, 0xAB, 0xA0, 0x9A, 0x9B, 0x9B, 0x8E, 0x83, 0x8B },
+ { 0x88, 0x78, 0x77, 0x77, 0x77, 0x76, 0x76, 0x73, 0x72, 0x74, 0x8B, 0xA8, 0xAC, 0xAE, 0xB1, 0xB0, 0xAF, 0xB3, 0xB4, 0xB1, 0xB6, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB6, 0xB6, 0xB5, 0xB6, 0xB6, 0xB3, 0x99, 0x9A, 0x9B, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9B, 0x9B, 0x9B, 0x9C, 0x9C, 0x9D, 0x9D, 0x9E, 0x9E, 0x9D, 0x9E, 0x9F, 0xA3, 0xA8, 0xA8, 0xA9, 0xAB, 0x9F, 0x98, 0x9A, 0x9A, 0x8D, 0x81, 0x88 },
+ { 0x8C, 0x7A, 0x7A, 0x7C, 0x7C, 0x7C, 0x7E, 0x7A, 0x7A, 0x7A, 0x88, 0x97, 0x97, 0x96, 0x98, 0x97, 0x99, 0xA1, 0xA5, 0xA6, 0xB1, 0xB4, 0xB4, 0xB5, 0xB5, 0xB4, 0xB5, 0xB5, 0xB4, 0xB5, 0xB5, 0xB3, 0x9A, 0x9A, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9D, 0x9D, 0xA0, 0xA1, 0xA5, 0xAA, 0xAB, 0xAD, 0xAD, 0xA3, 0x9D, 0x9E, 0x9E, 0x92, 0x87, 0x8C },
+ { 0x92, 0x7F, 0x7C, 0x7B, 0x7A, 0x7A, 0x7E, 0x7C, 0x7B, 0x7B, 0x82, 0x87, 0x85, 0x83, 0x85, 0x84, 0x84, 0x8A, 0x8E, 0x96, 0xAD, 0xB5, 0xB4, 0xB2, 0xB4, 0xB4, 0xB4, 0xB4, 0xB3, 0xB5, 0xB5, 0xB2, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A, 0x9A, 0x9A, 0x9B, 0x9B, 0x9C, 0x9C, 0x9C, 0x9D, 0x9D, 0x9D, 0x9E, 0x9E, 0xA1, 0xA3, 0xA8, 0xAE, 0xAE, 0xAF, 0xB0, 0xAD, 0xA9, 0xA8, 0xA9, 0xA1, 0x97, 0x9B },
+ { 0xA4, 0x92, 0x8D, 0x89, 0x85, 0x82, 0x82, 0x7F, 0x80, 0x81, 0x87, 0x89, 0x8A, 0x88, 0x8A, 0x89, 0x8A, 0x8F, 0x93, 0x9B, 0xAF, 0xB6, 0xB5, 0xB4, 0xB5, 0xB4, 0xB5, 0xB5, 0xB4, 0xB5, 0xB5, 0xB3, 0x9B, 0x9A, 0x99, 0x99, 0x9A, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9C, 0x9C, 0x9D, 0x9D, 0x9D, 0x9D, 0x9E, 0x9C, 0x9F, 0xA3, 0xAA, 0xB1, 0xB1, 0xB0, 0xB0, 0xB0, 0xAE, 0xAD, 0xAF, 0xAA, 0xA3, 0xA6 },
+ { 0xA8, 0x9E, 0x9F, 0xA0, 0xA2, 0xA1, 0xA1, 0x9F, 0xA0, 0x9E, 0x9E, 0x99, 0x9B, 0x99, 0x9C, 0x9D, 0x9D, 0xA0, 0xA2, 0xA5, 0xB1, 0xB4, 0xB5, 0xB6, 0xB5, 0xB4, 0xB5, 0xB4, 0xB3, 0xB4, 0xB4, 0xB2, 0x99, 0x98, 0x98, 0x97, 0x98, 0x99, 0x99, 0x99, 0x9A, 0x9A, 0x9A, 0x9B, 0x9C, 0x9D, 0x9D, 0x9E, 0x9F, 0xA0, 0xA3, 0xA6, 0xAA, 0xB0, 0xB0, 0xB0, 0xAE, 0xAD, 0xAC, 0xAC, 0xAF, 0xAB, 0xA4, 0xA9 },
+ { 0xA5, 0x9D, 0x9F, 0xA0, 0xA4, 0xA4, 0xA4, 0xA3, 0xA3, 0xA3, 0xA3, 0xA0, 0xA4, 0xA0, 0xA1, 0xA0, 0xA2, 0xA1, 0xA1, 0xA3, 0xAF, 0xB1, 0xB3, 0xB4, 0xB4, 0xB3, 0xB4, 0xB3, 0xB2, 0xB3, 0xB3, 0xB1, 0x8F, 0x8E, 0x8D, 0x8C, 0x8D, 0x8E, 0x8E, 0x8E, 0x91, 0x92, 0x94, 0x98, 0x9C, 0xA1, 0xA5, 0xA7, 0xA6, 0xA9, 0xAE, 0xAC, 0xAB, 0xAD, 0xAD, 0xB0, 0xB1, 0xAD, 0xAB, 0xAC, 0xB0, 0xAB, 0xA4, 0xA9 },
+ { 0xA9, 0x9A, 0x9C, 0xA4, 0xA5, 0xA6, 0xA2, 0x9D, 0xA0, 0xA0, 0x9F, 0x9F, 0x9F, 0x9F, 0xA0, 0xA0, 0xA0, 0xA1, 0xA0, 0xA2, 0xAA, 0xB0, 0xB2, 0xB4, 0xB3, 0xB2, 0xB0, 0xAF, 0xAF, 0xB1, 0xB2, 0xB1, 0x8B, 0x8A, 0x8A, 0x8C, 0x8D, 0x8F, 0x8F, 0x8A, 0x8B, 0x8E, 0x91, 0x95, 0x9B, 0xA2, 0xA5, 0xA7, 0xA6, 0xAA, 0xAE, 0xB0, 0xB0, 0xAF, 0xB0, 0xB1, 0xB1, 0xAE, 0xAC, 0xAC, 0xAC, 0xA7, 0xA2, 0xA8 },
+ { 0xAA, 0x9B, 0x9D, 0xA5, 0xA6, 0xA7, 0xA4, 0xA0, 0xA2, 0xA2, 0xA2, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA2, 0xA4, 0xA4, 0xA7, 0xAE, 0xB3, 0xB5, 0xB5, 0xB4, 0xB3, 0xB2, 0xB2, 0xB2, 0xB4, 0xB3, 0xB2, 0x96, 0x94, 0x95, 0x96, 0x96, 0x98, 0x97, 0x93, 0x95, 0x97, 0x99, 0x9C, 0xA0, 0xA5, 0xA7, 0xA8, 0xA9, 0xAC, 0xB0, 0xB1, 0xB0, 0xAF, 0xAF, 0xB0, 0xB1, 0xAE, 0xAD, 0xAD, 0xAE, 0xAA, 0xA5, 0xAB },
+ { 0xAA, 0x9C, 0x9D, 0xA5, 0xA6, 0xA7, 0xA6, 0xA3, 0xA4, 0xA4, 0xA5, 0xA6, 0xA7, 0xA6, 0xA6, 0xA5, 0xA5, 0xA6, 0xA9, 0xAC, 0xAF, 0xB2, 0xB4, 0xB3, 0xB5, 0xB4, 0xB1, 0xAF, 0xB0, 0xB1, 0xB0, 0xAF, 0xA2, 0xA1, 0xA2, 0xA2, 0xA2, 0xA4, 0xA3, 0xA1, 0xA3, 0xA4, 0xA5, 0xA7, 0xA9, 0xAD, 0xAE, 0xAE, 0xAD, 0xB0, 0xB3, 0xB3, 0xB3, 0xB1, 0xB1, 0xB1, 0xB4, 0xB2, 0xB1, 0xB0, 0xB1, 0xAC, 0xA7, 0xAB },
+ { 0xAA, 0x9B, 0x9D, 0xA4, 0xA4, 0xA5, 0xA5, 0xA4, 0xA4, 0xA5, 0xA6, 0xA7, 0xA7, 0xA7, 0xA7, 0xA6, 0xA7, 0xA7, 0xAA, 0xAD, 0xAC, 0xAE, 0xAF, 0xAD, 0xAF, 0xAC, 0xA6, 0xA2, 0xA3, 0xA4, 0xA4, 0xA4, 0xA1, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA5, 0xA4, 0xA6, 0xA7, 0xA8, 0xA9, 0xAC, 0xAF, 0xB0, 0xB0, 0xB0, 0xB2, 0xB4, 0xB5, 0xB5, 0xB4, 0xB4, 0xB4, 0xB6, 0xB4, 0xB4, 0xB3, 0xB4, 0xAF, 0xA8, 0xAB },
+ { 0xAB, 0x9C, 0x9D, 0xA4, 0xA3, 0xA4, 0xA4, 0xA5, 0xA4, 0xA4, 0xA5, 0xA6, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xAB, 0xAD, 0xAB, 0xAB, 0xAC, 0xAB, 0xAC, 0xA9, 0xA2, 0x9C, 0x9D, 0x9F, 0x9F, 0xA0, 0xA0, 0xA1, 0xA2, 0xA3, 0xA3, 0xA4, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAB, 0xAE, 0xB0, 0xB0, 0xB0, 0xB1, 0xB3, 0xB4, 0xB4, 0xB4, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB3, 0xB5, 0xB2, 0xAB, 0xAD },
+ { 0xAC, 0x9C, 0x9E, 0xA4, 0xA4, 0xA3, 0xA4, 0xA5, 0xA5, 0xA5, 0xA5, 0xA6, 0xA6, 0xA7, 0xA7, 0xA8, 0xA8, 0xA8, 0xAB, 0xAD, 0xAC, 0xAC, 0xAD, 0xAE, 0xAE, 0xAD, 0xA6, 0xA2, 0xA4, 0xA5, 0xA4, 0xA5, 0xA6, 0xA7, 0xA7, 0xA8, 0xA9, 0xA8, 0xA8, 0xAA, 0xA9, 0xAA, 0xAB, 0xAC, 0xAE, 0xB1, 0xB2, 0xB2, 0xB2, 0xB3, 0xB4, 0xB4, 0xB4, 0xB3, 0xB3, 0xB3, 0xB4, 0xB5, 0xB5, 0xB4, 0xB6, 0xB3, 0xAC, 0xAD },
+ { 0xAC, 0x9C, 0x9D, 0xA5, 0xA4, 0xA3, 0xA4, 0xA5, 0xA5, 0xA5, 0xA6, 0xA6, 0xA7, 0xA7, 0xA7, 0xA8, 0xA7, 0xA9, 0xAB, 0xAC, 0xAD, 0xAC, 0xAD, 0xAF, 0xAE, 0xAE, 0xA8, 0xA3, 0xA5, 0xA6, 0xA5, 0xA6, 0xA8, 0xA8, 0xA8, 0xA9, 0xAA, 0xA9, 0xA9, 0xAB, 0xAB, 0xAB, 0xAC, 0xAC, 0xAE, 0xB1, 0xB2, 0xB2, 0xB3, 0xB4, 0xB5, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB6, 0xB6, 0xB4, 0xB6, 0xB4, 0xAC, 0xAC },
+ { 0xAC, 0x9B, 0x9C, 0xA4, 0xA4, 0xA3, 0xA4, 0xA5, 0xA5, 0xA5, 0xA6, 0xA6, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA9, 0xAA, 0xAB, 0xAD, 0xAB, 0xAA, 0xAE, 0xAE, 0xAE, 0xA8, 0xA2, 0xA4, 0xA5, 0xA4, 0xA6, 0xA5, 0xA6, 0xA6, 0xA7, 0xA9, 0xA9, 0xA8, 0xAC, 0xAB, 0xAC, 0xAC, 0xAD, 0xAF, 0xB1, 0xB2, 0xB2, 0xB1, 0xB3, 0xB4, 0xB4, 0xB3, 0xB3, 0xB3, 0xB4, 0xB3, 0xB4, 0xB5, 0xB3, 0xB6, 0xB5, 0xAE, 0xAE },
+ { 0xAC, 0x9B, 0x9F, 0xA3, 0xA5, 0xA4, 0xA4, 0xA3, 0xA5, 0xA5, 0xA6, 0xA6, 0xA7, 0xA7, 0xA7, 0xA7, 0xA6, 0xA7, 0xA9, 0xAB, 0xAB, 0xAA, 0xAC, 0xAE, 0xAE, 0xAD, 0xA7, 0xA4, 0xA4, 0xA4, 0xA5, 0xA5, 0xA6, 0xA6, 0xA7, 0xA8, 0xA9, 0xA9, 0xAA, 0xAB, 0xAA, 0xAB, 0xAC, 0xAD, 0xAF, 0xB1, 0xB3, 0xB3, 0xB2, 0xB3, 0xB5, 0xB4, 0xB2, 0xB1, 0xB2, 0xB3, 0xB5, 0xB3, 0xB5, 0xB4, 0xB4, 0xB4, 0xAF, 0xAD },
+ { 0xAC, 0x9A, 0x9F, 0xA2, 0xA4, 0xA4, 0xA5, 0xA5, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA7, 0xA7, 0xA7, 0xA8, 0xAA, 0xAB, 0xAB, 0xAA, 0xAB, 0xAD, 0xAE, 0xAD, 0xA6, 0xA3, 0xA4, 0xA4, 0xA6, 0xA5, 0xA7, 0xA7, 0xA8, 0xA9, 0xA9, 0xA9, 0xAA, 0xAB, 0xAB, 0xAC, 0xAC, 0xAD, 0xAF, 0xB2, 0xB4, 0xB4, 0xB3, 0xB4, 0xB5, 0xB5, 0xB3, 0xB2, 0xB3, 0xB3, 0xB3, 0xB2, 0xB5, 0xB5, 0xB5, 0xB5, 0xB1, 0xAF },
+ { 0xB5, 0xA3, 0xA5, 0xA7, 0xA7, 0xA6, 0xA6, 0xA6, 0xA7, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA7, 0xA7, 0xA6, 0xA7, 0xA9, 0xAA, 0xAA, 0xA9, 0xAA, 0xAC, 0xAD, 0xAC, 0xA5, 0xA2, 0xA4, 0xA4, 0xA5, 0xA4, 0xA5, 0xA5, 0xA6, 0xA8, 0xA9, 0xA9, 0xAA, 0xAB, 0xAB, 0xAC, 0xAD, 0xAE, 0xB0, 0xB3, 0xB5, 0xB4, 0xB3, 0xB4, 0xB5, 0xB5, 0xB4, 0xB3, 0xB3, 0xB3, 0xB3, 0xB2, 0xB4, 0xB5, 0xB5, 0xB6, 0xB3, 0xB3 },
+ { 0xB5, 0xA2, 0xA4, 0xA6, 0xA7, 0xA7, 0xA7, 0xA7, 0xA6, 0xA7, 0xA7, 0xA8, 0xA8, 0xA7, 0xA6, 0xA6, 0xA3, 0xA5, 0xA7, 0xA9, 0xA9, 0xA9, 0xAA, 0xAB, 0xAC, 0xAA, 0xA3, 0xA1, 0xA3, 0xA3, 0xA4, 0xA2, 0xA0, 0xA1, 0xA3, 0xA6, 0xA8, 0xA9, 0xAA, 0xAC, 0xAA, 0xAC, 0xAD, 0xAF, 0xB2, 0xB4, 0xB4, 0xB3, 0xB1, 0xB2, 0xB3, 0xB4, 0xB3, 0xB2, 0xB2, 0xB2, 0xB4, 0xB3, 0xB4, 0xB4, 0xB5, 0xB6, 0xB4, 0xB6 },
+ { 0xB2, 0x9F, 0xA1, 0xA3, 0xA5, 0xA5, 0xA6, 0xA6, 0xA6, 0xA7, 0xA7, 0xA8, 0xA8, 0xA7, 0xA7, 0xA6, 0xA3, 0xA5, 0xA7, 0xA8, 0xA9, 0xAA, 0xAA, 0xAA, 0xAB, 0xAA, 0xA2, 0xA1, 0xA3, 0xA3, 0xA3, 0xA0, 0x9E, 0x9F, 0xA2, 0xA5, 0xA7, 0xA8, 0xA9, 0xAB, 0xAA, 0xAB, 0xAD, 0xAF, 0xB1, 0xB3, 0xB3, 0xB2, 0xAF, 0xB0, 0xB2, 0xB2, 0xB3, 0xB2, 0xB2, 0xB1, 0xB3, 0xB3, 0xB5, 0xB5, 0xB6, 0xB5, 0xB2, 0xB6 },
+ { 0xB4, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA7, 0xA7, 0xA7, 0xA7, 0xA4, 0xA6, 0xA8, 0xA9, 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0xAA, 0xA2, 0xA0, 0xA2, 0xA2, 0xA2, 0xA0, 0x9F, 0xA0, 0xA2, 0xA4, 0xA6, 0xA6, 0xA7, 0xA9, 0xA9, 0xAB, 0xAC, 0xAD, 0xAF, 0xB2, 0xB3, 0xB4, 0xB0, 0xB1, 0xB2, 0xB2, 0xB3, 0xB2, 0xB2, 0xB2, 0xB1, 0xB3, 0xB5, 0xB5, 0xB6, 0xB4, 0xB1, 0xB6 },
+ { 0xB5, 0xA0, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA4, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA7, 0xA7, 0xA6, 0xA3, 0xA6, 0xA8, 0xA8, 0xA9, 0xAA, 0xAA, 0xA9, 0xAB, 0xA9, 0xA1, 0x9F, 0xA0, 0xA0, 0xA1, 0x9F, 0xA0, 0xA0, 0xA1, 0xA3, 0xA4, 0xA4, 0xA5, 0xA6, 0xA7, 0xAA, 0xAC, 0xAD, 0xAF, 0xB2, 0xB4, 0xB4, 0xB0, 0xB1, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB0, 0xB2, 0xB4, 0xB3, 0xB4, 0xB3, 0xB1, 0xB8 },
+ { 0xBB, 0xA6, 0xA3, 0xA2, 0xA1, 0xA0, 0xA0, 0xA0, 0xA0, 0xA2, 0xA5, 0xA8, 0xA8, 0xA7, 0xA5, 0xA4, 0xA1, 0xA4, 0xA6, 0xA7, 0xA8, 0xAA, 0xAA, 0xA9, 0xAA, 0xA8, 0xA0, 0x9C, 0x9E, 0x9E, 0x9F, 0x9E, 0x9F, 0x9F, 0xA0, 0xA1, 0xA2, 0xA2, 0xA3, 0xA4, 0xA4, 0xA8, 0xAD, 0xAF, 0xB1, 0xB3, 0xB3, 0xB3, 0xB1, 0xB1, 0xB2, 0xB2, 0xB1, 0xB0, 0xB1, 0xB1, 0xB1, 0xB2, 0xB1, 0xB0, 0xB1, 0xB1, 0xB2, 0xBB },
+ { 0xB7, 0xAC, 0xA8, 0xA8, 0xA7, 0xA6, 0xA4, 0xA5, 0xA4, 0xA1, 0xA8, 0xAC, 0xAD, 0xA3, 0x86, 0x88, 0x83, 0x8B, 0x8F, 0x91, 0x96, 0x98, 0x99, 0x9D, 0x9D, 0x9E, 0x9F, 0x9F, 0x9D, 0x9C, 0x9D, 0x9F, 0x9F, 0xA0, 0xA1, 0xA1, 0xA2, 0xA3, 0xA3, 0xA3, 0xA4, 0xA8, 0xAD, 0xB0, 0xB1, 0xB3, 0xB5, 0xB6, 0xB3, 0xB2, 0xB2, 0xB1, 0xB1, 0xB2, 0xB2, 0xB3, 0xB1, 0xB1, 0xB3, 0xB1, 0xB0, 0xB0, 0xB2, 0xBA },
+ { 0xB5, 0xAE, 0xAD, 0xAE, 0xAD, 0xAD, 0xAC, 0xAD, 0xAC, 0xAC, 0xB1, 0xB0, 0xAF, 0xA6, 0x88, 0x83, 0x81, 0x8B, 0x91, 0x93, 0x98, 0x9B, 0x9D, 0xA0, 0x9F, 0xA0, 0xA1, 0xA1, 0xA0, 0x9F, 0xA0, 0xA1, 0xA2, 0xA2, 0xA3, 0xA4, 0xA4, 0xA5, 0xA5, 0xA5, 0xA6, 0xAA, 0xAE, 0xB0, 0xB1, 0xB3, 0xB5, 0xB6, 0xB3, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB4, 0xB2, 0xAF, 0xAE, 0xAE, 0xB5 },
+ { 0xB2, 0xAE, 0xAF, 0xAF, 0xAE, 0xAF, 0xAF, 0xB0, 0xB1, 0xB2, 0xB7, 0xB5, 0xB5, 0xB1, 0x91, 0x84, 0x85, 0x90, 0x96, 0x98, 0x9B, 0x9E, 0x9E, 0xA0, 0xA1, 0xA2, 0xA4, 0xA4, 0xA4, 0xA3, 0xA3, 0xA3, 0xA3, 0xA2, 0xA2, 0xA2, 0xA3, 0xA4, 0xA5, 0xA5, 0xA7, 0xAA, 0xAE, 0xB0, 0xB2, 0xB4, 0xB6, 0xB6, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB4, 0xB2, 0xB1, 0xAF, 0xAE, 0xB4 },
+ { 0xB1, 0xAE, 0xAE, 0xAC, 0xAB, 0xB0, 0xB2, 0xB3, 0xB5, 0xB5, 0xB6, 0xB5, 0xB6, 0xB2, 0x93, 0x81, 0x82, 0x8D, 0x95, 0x97, 0x9A, 0x9E, 0xA0, 0xA1, 0xA1, 0xA2, 0xA3, 0xA3, 0xA3, 0xA3, 0xA2, 0xA1, 0xA2, 0xA2, 0xA1, 0xA2, 0xA4, 0xA6, 0xA9, 0xAB, 0xAD, 0xAF, 0xB1, 0xB2, 0xB3, 0xB5, 0xB6, 0xB5, 0xB2, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB2, 0xB2, 0xB4, 0xB4, 0xB3, 0xB2, 0xB0, 0xB5 },
+ { 0xB2, 0xAE, 0xAD, 0xAB, 0xAD, 0xB3, 0xB6, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB5, 0xB2, 0x9C, 0x8E, 0x8D, 0x96, 0x9C, 0x9D, 0x9F, 0xA2, 0xA4, 0xA5, 0xA7, 0xA7, 0xA8, 0xA9, 0xA9, 0xA8, 0xA6, 0xA5, 0xA6, 0xA6, 0xA6, 0xA7, 0xA9, 0xAE, 0xB2, 0xB4, 0xB5, 0xB6, 0xB6, 0xB6, 0xB5, 0xB6, 0xB6, 0xB5, 0xB2, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB4, 0xB3, 0xB5, 0xB4, 0xB4, 0xB3, 0xB0, 0xB4 },
+ { 0xB2, 0xAD, 0xAD, 0xAC, 0xAE, 0xB3, 0xB3, 0xB3, 0xB2, 0xB3, 0xB5, 0xB8, 0xB6, 0xB7, 0xB1, 0xAD, 0xAE, 0xB2, 0xB4, 0xB2, 0xB0, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB2, 0xB3, 0xB2, 0xB1, 0xB0, 0xAE, 0xAD, 0xAC, 0xAB, 0xAB, 0xAC, 0xAF, 0xB1, 0xB3, 0xB3, 0xB5, 0xB6, 0xB5, 0xB5, 0xB6, 0xB6, 0xB5, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB4, 0xB4, 0xB4, 0xB3, 0xB0, 0xB4 },
+ { 0xB3, 0xAF, 0xB0, 0xAF, 0xAF, 0xB0, 0xAF, 0xAF, 0xB0, 0xB1, 0xB3, 0xB7, 0xB4, 0xB7, 0xB7, 0xB7, 0xB4, 0xB4, 0xB4, 0xB4, 0xB3, 0xB3, 0xB3, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB5, 0xB4, 0xB3, 0xB2, 0xB1, 0xB0, 0xAF, 0xAE, 0xAE, 0xAE, 0xAF, 0xAF, 0xAF, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB6, 0xB6, 0xB2, 0xB2, 0xB2, 0xB1, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB2, 0xB4, 0xB4, 0xB5, 0xB4, 0xB3, 0xB7 },
+ { 0xB7, 0xB3, 0xB5, 0xB4, 0xB3, 0xB4, 0xB3, 0xB4, 0xB3, 0xB4, 0xB4, 0xB8, 0xB4, 0xB6, 0xB6, 0xB3, 0xB2, 0xB0, 0xB0, 0xB1, 0xB2, 0xB3, 0xB5, 0xB6, 0xB4, 0xB5, 0xB6, 0xB6, 0xB6, 0xB5, 0xB4, 0xB4, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB1, 0xB4, 0xB5, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB2, 0xB2, 0xB1, 0xB1, 0xB1, 0xB2, 0xB3, 0xB3, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB5, 0xB4, 0xB9 },
+ { 0xB6, 0xB3, 0xB3, 0xB4, 0xB3, 0xB3, 0xB4, 0xB3, 0xB3, 0xB4, 0xB5, 0xB6, 0xB5, 0xB5, 0xB6, 0xB6, 0xB3, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB7, 0xB7, 0xB8, 0xB7, 0xB6, 0xB4, 0xB4, 0xB3, 0xB3, 0xB4, 0xB4, 0xB4, 0xB3, 0xB3, 0xB3, 0xB4, 0xB3, 0xB4, 0xB5, 0xB5, 0xB4, 0xB4, 0xB7, 0xB1, 0xB1, 0xB1, 0xB1, 0xB2, 0xB2, 0xB3, 0xB3, 0xB4, 0xB5, 0xB5, 0xB5, 0xB5, 0xB4, 0xB4, 0xB7 },
+ { 0xB6, 0xB3, 0xB3, 0xB4, 0xB3, 0xB3, 0xB4, 0xB3, 0xB3, 0xB4, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB6, 0xB4, 0xB4, 0xB5, 0xB5, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB5, 0xB4, 0xB2, 0xB3, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB3, 0xB4, 0xB5, 0xB4, 0xB3, 0xB2, 0xB4, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB5, 0xB5, 0xB6, 0xB4, 0xB4, 0xB7 },
+ { 0xB6, 0xB3, 0xB3, 0xB4, 0xB3, 0xB3, 0xB4, 0xB4, 0xB3, 0xB4, 0xB5, 0xB5, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB2, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB5, 0xB4, 0xB4, 0xB3, 0xB2, 0xB2, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB5, 0xB5, 0xB6, 0xB6, 0xB5, 0xB6, 0xB5, 0xB5, 0xB8 },
+ { 0xB6, 0xB3, 0xB3, 0xB4, 0xB3, 0xB4, 0xB5, 0xB4, 0xB4, 0xB5, 0xB5, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB8, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB4, 0xB5, 0xB5, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB2, 0xB3, 0xB2, 0xB2, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB6, 0xB9 },
+ { 0xB7, 0xB4, 0xB3, 0xB4, 0xB3, 0xB4, 0xB5, 0xB4, 0xB4, 0xB5, 0xB6, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB8, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB8, 0xB5, 0xB5, 0xB6, 0xB6, 0xB5, 0xB4, 0xB4, 0xB4, 0xB3, 0xB4, 0xB1, 0xB2, 0xB2, 0xB1, 0xB4, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB9 },
+ { 0xB7, 0xB4, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB3, 0xB4, 0xB5, 0xB6, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB4, 0xB4, 0xB6, 0xB2, 0xB3, 0xB3, 0xB2, 0xB5, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB6, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xBA },
+ { 0xB8, 0xB4, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB3, 0xB4, 0xB4, 0xB5, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB5, 0xB5, 0xB4, 0xB4, 0xB5, 0xB4, 0xB3, 0xB2, 0xB4, 0xB4, 0xAD, 0xAC, 0xAD, 0xAE, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB6, 0xB6, 0xB7, 0xBA },
+ { 0xB8, 0xB5, 0xB4, 0xB5, 0xB4, 0xB4, 0xB4, 0xB2, 0xB3, 0xB4, 0xB5, 0xB5, 0xB4, 0xB3, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB5, 0xB4, 0xB4, 0xB4, 0xB3, 0xB2, 0xB0, 0xB0, 0xAF, 0xA4, 0xA2, 0xA3, 0xA7, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB6, 0xB5, 0xB5, 0xB6, 0xB6, 0xB7, 0xBA },
+ { 0xBA, 0xB5, 0xB5, 0xB6, 0xB5, 0xB6, 0xB4, 0xAE, 0xB0, 0xB1, 0xB2, 0xB3, 0xB3, 0xB3, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB5, 0xB5, 0xB4, 0xB4, 0xB3, 0xAF, 0xAA, 0xAB, 0xAC, 0x9C, 0x99, 0x9B, 0xA2, 0xB3, 0xB2, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB5, 0xB7, 0xB7, 0xB7, 0xB6, 0xB4, 0xBB },
+ { 0xB8, 0xB5, 0xB5, 0xB6, 0xB5, 0xB5, 0xB4, 0xAE, 0xB0, 0xB1, 0xB2, 0xB3, 0xB3, 0xB4, 0xB4, 0xB5, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB8, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB5, 0xB5, 0xB3, 0xB0, 0xAC, 0xAC, 0xAD, 0x9C, 0x98, 0x9A, 0xA1, 0xB3, 0xB4, 0xB4, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB7, 0xB5, 0xB6, 0xB5, 0xB6, 0xB7, 0xB6, 0xBB },
+ { 0xB9, 0xB5, 0xB5, 0xB6, 0xB5, 0xB6, 0xB6, 0xB2, 0xB4, 0xB4, 0xB5, 0xB6, 0xB6, 0xB5, 0xB5, 0xB4, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB4, 0xB4, 0xB4, 0xB3, 0xB3, 0xB2, 0xB0, 0xAD, 0xAF, 0xAF, 0xA0, 0x9D, 0x9E, 0xA2, 0xB2, 0xB3, 0xB4, 0xB5, 0xB5, 0xB6, 0xB5, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB4, 0xB5, 0xB7, 0xB6, 0xB8 },
+ { 0xB1, 0xAD, 0xAD, 0xAF, 0xAF, 0xB2, 0xB6, 0xB5, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB5, 0xB6, 0xB6, 0xB5, 0xB5, 0xB5, 0xB4, 0xB3, 0xB2, 0xB1, 0xB0, 0xA3, 0x9F, 0x9E, 0xA1, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB2, 0xB3 },
+ { 0xB1, 0xAC, 0xAD, 0xAE, 0xAD, 0xB0, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB6, 0xB6, 0xB7, 0xB7, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB5, 0xB6, 0xB6, 0xB5, 0xB4, 0xB4, 0xB3, 0xB2, 0xB2, 0xB4, 0xB1, 0xA3, 0x9F, 0x9E, 0xA1, 0xAE, 0xB1, 0xB1, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB3, 0xB4, 0xB4 },
+ { 0xB0, 0xAC, 0xAD, 0xAF, 0xAF, 0xB0, 0xB4, 0xB4, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB4, 0xB2, 0xB1, 0xB0, 0xAF, 0xAE, 0xAD, 0xAD, 0xAC, 0xAA, 0xA1, 0x9F, 0x9F, 0xA0, 0xA9, 0xAB, 0xAB, 0xAB, 0xAC, 0xAF, 0xB1, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB3, 0xB4, 0xB4, 0xB3, 0xB4, 0xB1 },
+ { 0x9C, 0x98, 0x9B, 0xA1, 0xA4, 0xA9, 0xB0, 0xB3, 0xB4, 0xB5, 0xB6, 0xB6, 0xB5, 0xB5, 0xB5, 0xB6, 0xB8, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xA4, 0xA3, 0xA1, 0xA2, 0xA1, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x9C, 0x9E, 0x9E, 0x9C, 0xA0, 0x9F, 0xA0, 0xA0, 0xA1, 0xA5, 0xA9, 0xAD, 0xAD, 0xAD, 0xAC, 0xAE, 0xAD, 0xAD, 0xAD, 0xAE, 0xAB, 0xA0 },
+ { 0x8D, 0x88, 0x8A, 0x91, 0x95, 0x9D, 0xA7, 0xAB, 0xAF, 0xB1, 0xB3, 0xB4, 0xB4, 0xB5, 0xB6, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xA0, 0x9E, 0x9D, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0xA0, 0x9F, 0x9C, 0x9F, 0x9F, 0x9C, 0x9E, 0x9D, 0x9E, 0x9D, 0x9E, 0xA1, 0xA6, 0xA8, 0xA8, 0xA7, 0xA8, 0xAD, 0xAC, 0xAB, 0xAB, 0xAD, 0xA5, 0x92 },
+ { 0x8E, 0x87, 0x8A, 0x91, 0x93, 0x9B, 0xA7, 0xAC, 0xAE, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB6, 0xB6, 0xB8, 0xB6, 0xB5, 0xB6, 0xB3, 0xB3, 0xB8, 0xB7, 0xB7, 0xB8, 0xB7, 0xB6, 0xB6, 0xB7, 0xB5, 0xA2, 0x9F, 0x9F, 0xA0, 0x9F, 0x9F, 0x9F, 0x9F, 0xA0, 0x9F, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9E, 0x9C, 0x9E, 0xA3, 0xA7, 0xA8, 0xA8, 0xA7, 0xA9, 0xAD, 0xAC, 0xAB, 0xAA, 0xAB, 0xA8, 0x94 },
+ { 0x8D, 0x87, 0x89, 0x8E, 0x90, 0x97, 0xA2, 0xA6, 0xAC, 0xAD, 0xAF, 0xAF, 0xB0, 0xB3, 0xB5, 0xB6, 0xB4, 0xB7, 0xB7, 0xB6, 0xB5, 0xB3, 0xB3, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB7, 0xB7, 0xB5, 0xA0, 0x9E, 0x9E, 0x9F, 0x9E, 0x9E, 0x9F, 0x9F, 0x9F, 0x9E, 0x9D, 0x9C, 0x9D, 0x9D, 0x9D, 0x9C, 0x9D, 0x9C, 0x9D, 0xA0, 0xA4, 0xA6, 0xA8, 0xA9, 0xA5, 0xAA, 0xAC, 0xAC, 0xAA, 0xA8, 0x9F, 0x8C },
+ { 0x8C, 0x86, 0x88, 0x8D, 0x8F, 0x95, 0x9E, 0xA2, 0xA5, 0xA7, 0xA8, 0xA8, 0xA8, 0xA9, 0xAB, 0xAC, 0xAF, 0xB2, 0xB4, 0xB5, 0xB5, 0xB5, 0xB4, 0xB3, 0xB5, 0xB4, 0xB5, 0xB5, 0xB4, 0xB5, 0xB6, 0xB3, 0x9E, 0x9C, 0x9E, 0xA0, 0x9F, 0xA0, 0xA0, 0xA0, 0xA0, 0x9F, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9D, 0x9C, 0x9C, 0x9D, 0x9F, 0xA1, 0xA3, 0xA5, 0xA6, 0xAA, 0xAA, 0xAB, 0xA8, 0xA7, 0xA9, 0x9D, 0x8C },
+ { 0x87, 0x82, 0x85, 0x8B, 0x8E, 0x95, 0x9D, 0xA1, 0xA5, 0xA7, 0xA9, 0xA9, 0xA9, 0xA9, 0xAA, 0xAA, 0xA6, 0xA8, 0xAA, 0xAC, 0xAF, 0xB2, 0xB3, 0xB1, 0xB3, 0xB3, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB2, 0x99, 0x98, 0x9B, 0x9E, 0x9E, 0x9E, 0x9F, 0x9E, 0xA0, 0x9E, 0x9D, 0x9D, 0x9D, 0x9D, 0x9C, 0x9C, 0x99, 0x9A, 0x9C, 0x9E, 0xA1, 0xA4, 0xA6, 0xA5, 0xA5, 0xA6, 0xAB, 0xA6, 0xA8, 0xAC, 0x98, 0x86 },
+ { 0x84, 0x80, 0x83, 0x8A, 0x8E, 0x94, 0x9C, 0x9F, 0xA3, 0xA6, 0xA8, 0xA8, 0xA9, 0xAA, 0xAC, 0xAC, 0xA8, 0xAC, 0xAD, 0xAD, 0xB0, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB4, 0xB5, 0xB5, 0xB5, 0xB4, 0xB1, 0x97, 0x97, 0x9A, 0x9D, 0x9E, 0x9E, 0x9E, 0x9D, 0x9F, 0x9E, 0x9C, 0x9C, 0x9C, 0x9D, 0x9C, 0x9B, 0x9A, 0x9A, 0x9B, 0x9D, 0xA1, 0xA5, 0xA6, 0xA6, 0xA3, 0xA6, 0xAC, 0xA3, 0xA4, 0xA9, 0x97, 0x89 },
+ { 0x85, 0x80, 0x81, 0x86, 0x88, 0x8A, 0x8E, 0x91, 0x91, 0x93, 0x94, 0x94, 0x93, 0x95, 0x97, 0x99, 0x9C, 0xA4, 0xA9, 0xAD, 0xB3, 0xB3, 0xB1, 0xB2, 0xB1, 0xB1, 0xB3, 0xB4, 0xB3, 0xB4, 0xB3, 0xAF, 0x97, 0x96, 0x99, 0x9C, 0x9C, 0x9C, 0x9C, 0x9B, 0x9C, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9A, 0x9A, 0x97, 0x96, 0x96, 0x99, 0x9E, 0xA1, 0xA4, 0xA6, 0xA6, 0xA7, 0xA4, 0x93, 0x8E, 0x91, 0x89, 0x87 },
+ { 0x85, 0x80, 0x7F, 0x82, 0x81, 0x81, 0x82, 0x83, 0x83, 0x85, 0x87, 0x86, 0x85, 0x85, 0x86, 0x87, 0x84, 0x8B, 0x91, 0x9D, 0xAF, 0xB3, 0xB0, 0xB1, 0xB2, 0xB2, 0xB3, 0xB3, 0xB2, 0xB3, 0xB2, 0xB0, 0x98, 0x96, 0x97, 0x99, 0x99, 0x98, 0x98, 0x97, 0x98, 0x99, 0x9A, 0x9A, 0x9A, 0x99, 0x98, 0x98, 0x95, 0x93, 0x91, 0x91, 0x8E, 0x8A, 0x89, 0x8B, 0x8D, 0x8E, 0x8B, 0x83, 0x81, 0x82, 0x80, 0x85 },
+ { 0x8A, 0x85, 0x85, 0x87, 0x87, 0x86, 0x87, 0x88, 0x87, 0x8B, 0x8E, 0x8F, 0x8E, 0x8E, 0x8D, 0x8D, 0x89, 0x89, 0x86, 0x94, 0xAF, 0xB9, 0xB5, 0xB7, 0xB5, 0xB5, 0xB6, 0xB5, 0xB4, 0xB4, 0xB5, 0xB3, 0x9E, 0x9B, 0x9B, 0x9C, 0x9B, 0x9A, 0x9A, 0x99, 0x9B, 0x9D, 0x9F, 0x9F, 0x9F, 0x9E, 0x9D, 0x9D, 0x99, 0x98, 0x98, 0x98, 0x92, 0x8A, 0x87, 0x8A, 0x88, 0x87, 0x85, 0x85, 0x88, 0x86, 0x85, 0x8D },
+};
+
+struct CheesyRand
+{
+ CheesyRand( int seed = 12345 )
+ {
+ z = seed | 0x00010000;
+ w = ~seed | 0x00000100;
+ }
+
+ uint32 RandInt()
+ {
+ // http://en.wikipedia.org/wiki/Random_number_generation#Computational_methods
+ z = 36969 * (z & 65535) + (z >> 16);
+ w = 18000 * (w & 65535) + (w >> 16);
+ return (z << 16) + w;
+ }
+
+ inline float RandFloat01()
+ {
+ return RandInt() / 4294970000.0f;
+ }
+
+ inline float RandFloatNeg1To1()
+ {
+ return RandFloat01() * 2.0f - 1.0f;
+ }
+
+ uint32 z, w;
+};
+
+void CConfirmCustomizeTextureDialog::PerformPainterlyFilter()
+{
+
+ // Resample it to 2x the final resolution. Having a fixed resolution
+ // for the "source" image makes it easier, since we can use fixed size
+ // kernels, etc
+ Bitmap_t imageTemp1, imageTemp2;
+ ImgUtl_ResizeBitmap( imageTemp1, k_nCustomImageSize*2, k_nCustomImageSize*2, &m_imgSquare );
+
+ //
+ // Shape correction V1:
+ //
+ #if 1
+ // Perform symmetric nearest neighbor
+ float filterStrength = .95f;
+ SymmetricNearestNeighborFilter( imageTemp1, imageTemp2, 5, filterStrength );
+ BilateralFilter( imageTemp2, imageTemp1, 4, .5, .15 );
+ BilateralFilter( imageTemp1, imageTemp2, 2, .9, .7 );
+ imageTemp1.SetPixelData( imageTemp2 );
+ #endif
+
+ //
+ // Shape correction V2:
+ //
+ #if 0
+ // Perform symmetric nearest neighbor
+ float snnFilterStrength = .7f;
+ SymmetricNearestNeighborFilter( imageTemp1, imageTemp2, 4, snnFilterStrength );
+
+ // And some bilateral filtering to smooth it
+ BilateralFilter( imageTemp2, imageTemp1, 2, .75, .5 );
+ BilateralFilter( imageTemp1, imageTemp2, 3, .7, .3 );
+ BilateralFilter( imageTemp2, imageTemp1, 4, .6, .2 );
+ #endif
+
+// // Load up brush strokes
+// if ( !m_imgBrushStrokes.IsValid() )
+// {
+// m_imgBrushStrokes.Load( "d:\\texture.jpg" );
+//
+// for (int y = 0 ; y < m_imgBrushStrokes.Height() ; ++y )
+// {
+// for (int x = 0 ; x < m_imgBrushStrokes.Width() ; ++x )
+// {
+// Warning("0x%02X, ", m_imgBrushStrokes.GetColor(x,y).r() );
+// }
+// Warning("\n");
+// }
+//
+// m_imgBrushStrokes.Resize( m_imgTemp.Width(), m_imgTemp.Height() );
+// }
+
+ //
+ // Color correction
+ //
+ for ( int y = 0 ; y < imageTemp1.Height() ; ++y )
+ {
+ for ( int x = 0 ; x < imageTemp1.Width() ; ++x )
+ {
+
+ // Fetch original pixel in RGB space
+ Color c = imageTemp1.GetColor( x,y );
+ Vector rgb((float)c.r(), (float)c.g(), (float)c.b());
+
+ // Convert to HSV
+ Vector hsv;
+ RGBtoHSV( rgb, hsv );
+
+ //
+ // Color correction V1
+ //
+ #if 0
+ // Shift towards red, away from blue
+ //rgb.x += rgb.z * .2f;
+ //rgb.z *= 0.7f;
+ // Desaturate
+ float satMult = .65; // desaturate
+ hsv.y *= satMult;
+ #endif
+
+ //
+ // Color correction V2
+ //
+
+ #if 1
+ static const Color swatches[] =
+ {
+ Color( 183, 224, 252, 255 ), // sky light
+ Color( 83, 109, 205, 255 ), // sky med
+ Color( 64, 68, 195, 255 ), // sky dark
+ Color( 100, 68, 57, 255 ), // skin demo
+ Color( 139, 101, 84, 255 ), // skin demo light
+ Color( 133, 105, 68, 255 ), // saxton hair
+ Color( 252, 169, 131, 255 ), // skin light
+ Color( 194, 132, 106, 255 ), // skin
+
+ Color( 255, 255, 255, 255 ),
+ Color( 246, 231, 222, 255 ),
+ Color( 218, 189, 171, 255 ),
+ Color( 193, 161, 138, 255 ),
+
+ Color( 248, 185, 138, 255 ),
+ Color( 245, 173, 135, 255 ),
+ Color( 239, 152, 73, 255 ),
+ Color( 241, 129, 73, 255 ),
+
+ Color( 106, 69, 52, 255 ),
+ Color( 145, 58, 31, 255 ),
+ Color( 189, 58, 58, 255 ),
+ Color( 157, 48, 47, 255 ),
+ Color( 69, 44, 37, 255 ),
+
+ Color( 107, 106, 101, 255 ),
+ Color( 118, 138, 136, 255 ),
+ Color( 91, 122, 140, 255 ),
+ Color( 56, 92, 120, 255 ),
+ Color( 52, 47, 44, 255 ),
+ };
+
+ static float selfWeight = .15f;
+ static float thresh = .60f;
+ Vector rgb2((float)c.r(), (float)c.g(), (float)c.b());
+ float totalWeight = selfWeight;
+ rgb2 *= selfWeight;
+ for ( int i = 0 ; i < ARRAYSIZE(swatches) ; ++i )
+ {
+ float similarity = 1.0f - ApproxColorDist( c, swatches[i] ) - thresh;
+ if ( similarity > 0.0f )
+ {
+ similarity /= (1.0f - thresh); // get in 0...1 scale
+ similarity *= similarity*similarity;
+ rgb2.x += similarity*(float)swatches[i].r();
+ rgb2.y += similarity*(float)swatches[i].g();
+ rgb2.z += similarity*(float)swatches[i].b();
+ totalWeight += similarity;
+ }
+ }
+ rgb2 /= totalWeight;
+
+ // Calc hue for the shifted one
+ Vector hsv2;
+ RGBtoHSV( rgb2, hsv2 );
+
+ // Replace hue and saturation
+ hsv.x = hsv2.x;
+ hsv.y = hsv2.y;
+ #endif
+ // Convert back to RGB space
+ HSVtoRGB( hsv, rgb );
+
+ // Overlay brush stroke noise
+ Vector overlayValue;
+ int brushX = x * k_BrushStrokeSize / imageTemp1.Width();
+ int brushY = y * k_BrushStrokeSize / imageTemp1.Height();
+ //float k = (float)m_imgBrushStrokes.GetColor( x, y ).r() / 255.0f;
+ float k = (float)s_bBrushStrokeData[brushY][brushX] / 255.0f;
+ if ( k < .5f )
+ {
+ overlayValue = rgb * k * 2.0f;
+ }
+ else
+ {
+ Vector kWhite( 255.0f, 255.0f, 255.0f );
+ float q = 2.0f * ( 1.0f - k ); // 0.5 -> 1.0 , 1.0 -> 0
+ overlayValue = kWhite - ( kWhite - rgb ) * q;
+ }
+
+ float overlayStrength = .10f;
+ rgb += (overlayValue - rgb) * overlayStrength;
+
+ // Put back into the image
+ Color result(
+ (unsigned char)clamp(rgb.x, 0.0f, 255.0f),
+ (unsigned char)clamp(rgb.y, 0.0f, 255.0f),
+ (unsigned char)clamp(rgb.z, 0.0f, 255.0f),
+ c.a()
+ );
+ imageTemp2.SetColor( x, y, result );
+ }
+ }
+
+ // Now downsample to the final size
+ ImgUtl_ResizeBitmap( m_imgFinal, k_nCustomImageSize, k_nCustomImageSize, &imageTemp2 );
+
+ // Add noise to the final image
+ // Use deterministic random number generator (i.e. let's not call rand()),
+ // so uploading the same image twice will produce the same hash
+ CheesyRand noiseRand;
+ for ( int y = 0 ; y < m_imgFinal.Height() ; ++y )
+ {
+ for ( int x = 0 ; x < m_imgFinal.Width() ; ++x )
+ {
+ float noiseStrength = 2.0f;
+ int noise = (int)floor( noiseRand.RandFloatNeg1To1() * noiseStrength + .5f );
+ Color c = m_imgFinal.GetColor( x, y );
+ Color result(
+ clamp( c.r() + noise, 0, 255 ),
+ clamp( c.g() + noise, 0, 255 ),
+ clamp( c.b() + noise, 0, 255 ),
+ c.a()
+ );
+ m_imgFinal.SetColor( x, y, result );
+ }
+ }
+}
+
+#ifdef TEST_FILTERS
+void CConfirmCustomizeTextureDialog::TestFilters()
+{
+ const char *szTestImageFilenames[] =
+ {
+ "d:/custom_images/borat.jpg",
+ "d:/custom_images/cloud_strife-profile.jpg",
+ "d:/custom_images/ladies_man.png",
+ "d:/custom_images/dota_hero.jpg",
+ "d:/custom_images/elmo balls.jpg",
+ "d:/custom_images/halolz-dot-com-teamfortress2-sexyheavy-prematureubers.jpg",
+ "d:/custom_images/doug_loves_movies.jpg",
+ "d:/custom_images/lolcat.jpg",
+ "d:/custom_images/mario_3d.jpg",
+ //"d:/custom_images/pulp_fiction_sam.gif",
+ "d:/custom_images/RainbowBright.jpg",
+ "d:/custom_images/elliot_and_travis.tga",
+ "d:/custom_images/give_peace_a_chance.jpg",
+ };
+ const int k_nTestImages = ARRAYSIZE(szTestImageFilenames);
+
+ Bitmap_t imageOutput;
+ imageOutput.Init( k_nCustomImageSize*3, k_nCustomImageSize*k_nTestImages, IMAGE_FORMAT_RGBA8888 );
+
+ for ( int i = 0 ; i < k_nTestImages ; ++i )
+ {
+ ConversionErrorType nErrorCode = ImgUtl_LoadBitmap( szTestImageFilenames[i], m_imgSource );
+ if ( nErrorCode != CE_SUCCESS )
+ {
+ Assert( nErrorCode == CE_SUCCESS );
+ continue;
+ }
+
+ PerformSquarize();
+ PerformPainterlyFilter();
+ int y = i*k_nCustomImageSize;
+ imageOutput.SetPixelData( m_imgSquareDisplay, 0, y );
+ imageOutput.SetPixelData( m_imgFinal, k_nCustomImageSize, y );
+ }
+
+ CUtlBuffer pngFileData;
+ ImgUtl_SavePNGBitmapToBuffer( pngFileData, imageOutput );
+
+ g_pFullFileSystem->WriteFile( "d:/painterly.png", NULL, pngFileData );
+}
+#endif
+
+void CConfirmCustomizeTextureDialog::RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect )
+{
+
+ // Check if we need to redo the filter
+ CleanFilteredImage();
+
+ Assert( pVTFTexture->FrameCount() == 1 );
+ Assert( pVTFTexture->FaceCount() == 1 );
+ Assert( pTexture == g_pPreviewCustomTexture );
+ Assert( !pTexture->IsMipmapped() );
+
+ int nWidth, nHeight, nDepth;
+ pVTFTexture->ComputeMipLevelDimensions( 0, &nWidth, &nHeight, &nDepth );
+ Assert( nDepth == 1 );
+ Assert( nWidth == m_imgFinal.Width() && nHeight == m_imgFinal.Height() );
+
+ CPixelWriter pixelWriter;
+ pixelWriter.SetPixelMemory( pVTFTexture->Format(),
+ pVTFTexture->ImageData( 0, 0, 0 ), pVTFTexture->RowSizeInBytes( 0 ) );
+
+ // !SPEED! 'Tis probably DEATHLY slow...
+ for ( int y = 0; y < nHeight; ++y )
+ {
+ pixelWriter.Seek( 0, y );
+ for ( int x = 0; x < nWidth; ++x )
+ {
+ Color c = m_imgFinal.GetColor( x, y );
+ pixelWriter.WritePixel( c.r(), c.g(), c.b(), c.a() );
+ }
+ }
+
+ // We're no longer dirty
+ g_pPreviewCustomTextureDirty = false;
+}
+
+void CConfirmCustomizeTextureDialog::Release()
+{
+ if ( g_pPreviewCustomTexture )
+ {
+ ITexture *tex = g_pPreviewCustomTexture;
+ g_pPreviewCustomTexture = NULL; // clear pointer first, to prevent infinite recursion
+ tex->SetTextureRegenerator( NULL );
+ tex->Release();
+ }
+ g_pPreviewEconItem = NULL;
+}
+
+class CCustomizeTextureJobDialog : public CApplyCustomTextureJob
+{
+public:
+ CCustomizeTextureJobDialog( const void *pPNGData, int nPNGDataBytes, CConfirmCustomizeTextureDialog *pDlg )
+ : CApplyCustomTextureJob( pDlg->GetToolItem()->GetItemID(), pDlg->GetSubjectItem()->GetItemID(), pPNGData, nPNGDataBytes )
+ , m_pDlg( pDlg )
+ {
+ }
+
+protected:
+
+ virtual EResult YieldingRunJob()
+ {
+ // Base class do the work
+ EResult result = CApplyCustomTextureJob::YieldingRunJob();
+
+ CloseWaitingDialog();
+
+ // Show result
+ if ( result == k_EResultOK )
+ {
+ m_pDlg->OnCommand("close");
+ }
+ else
+ {
+ m_pDlg->CloseWithGenericError();
+ }
+
+ // Return status code
+ return result;
+ }
+
+ CConfirmCustomizeTextureDialog *m_pDlg;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmCustomizeTextureDialog::Apply( void )
+{
+ Assert( m_imgFinal.IsValid() );
+
+ // Throw up a busy dialog
+ SetPage( ePage_PerformingAction );
+
+ // Write PNG data
+ CUtlBuffer bufPNGData;
+ if ( ImgUtl_SavePNGBitmapToBuffer( bufPNGData, m_imgFinal ) != CE_SUCCESS )
+ {
+ Warning( "Failed to write PNG\n" );
+ CloseWithGenericError();
+ return;
+ }
+
+ // Stats
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "customized_texture" );
+
+ // Start a job to do the async work
+ CCustomizeTextureJobDialog *pJob = new CCustomizeTextureJobDialog( bufPNGData.Base(), bufPNGData.TellPut(), this );
+ pJob->StartJob( NULL );
+}
+
+void CConfirmCustomizeTextureDialog::CloseWithGenericError()
+{
+ CloseWaitingDialog();
+
+ // Show error message dialog
+ ShowMessageBox( "#ToolCustomizeTextureError", "#ToolCustomizeTextureErrorMsg", "#GameUI_OK" );
+
+ // Close this window
+ OnCommand("close");
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmCustomizeTextureDialog::ConversionError( ConversionErrorType nError )
+{
+ const char *pErrorText = NULL;
+
+ switch ( nError )
+ {
+ case CE_MEMORY_ERROR:
+ pErrorText = "#GameUI_Spray_Import_Error_Memory";
+ break;
+
+ case CE_CANT_OPEN_SOURCE_FILE:
+ pErrorText = "#GameUI_Spray_Import_Error_Reading_Image";
+ break;
+
+ case CE_ERROR_PARSING_SOURCE:
+ pErrorText = "#GameUI_Spray_Import_Error_Image_File_Corrupt";
+ break;
+
+ case CE_SOURCE_FILE_SIZE_NOT_SUPPORTED:
+ pErrorText = "#GameUI_Spray_Import_Image_Wrong_Size";
+ break;
+
+ case CE_SOURCE_FILE_FORMAT_NOT_SUPPORTED:
+ pErrorText = "#GameUI_Spray_Import_Image_Wrong_Size";
+ break;
+
+ case CE_SOURCE_FILE_TGA_FORMAT_NOT_SUPPORTED:
+ pErrorText = "#GameUI_Spray_Import_Error_TGA_Format_Not_Supported";
+ break;
+
+ case CE_SOURCE_FILE_BMP_FORMAT_NOT_SUPPORTED:
+ pErrorText = "#GameUI_Spray_Import_Error_BMP_Format_Not_Supported";
+ break;
+
+ case CE_ERROR_WRITING_OUTPUT_FILE:
+ pErrorText = "#GameUI_Spray_Import_Error_Writing_Temp_Output";
+ break;
+
+ case CE_ERROR_LOADING_DLL:
+ pErrorText = "#GameUI_Spray_Import_Error_Cant_Load_VTEX_DLL";
+ break;
+ }
+
+ if ( pErrorText )
+ {
+ ShowMessageBox( "#ToolCustomizeTextureError", pErrorText, "#GameUI_OK" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmCustomizeTextureDialog::OnFileSelected(const char *fullpath)
+{
+ // this can take a while, put up a waiting cursor
+ vgui::surface()->SetCursor( vgui::dc_hourglass );
+
+ // they apparently don't want to use their avatar
+ m_bUseAvatar = false;
+
+ // Will need to be restretched/cropped/filtered, no matter what happens next
+ MarkSquareImageDirty();
+
+ // Load up the data as raw RGBA
+ ConversionErrorType nErrorCode = ImgUtl_LoadBitmap( fullpath, m_imgSource );
+ if ( nErrorCode != CE_SUCCESS )
+ {
+ // Report error, if any
+ ConversionError( nErrorCode );
+ }
+
+ // Slam alpha to 255. We do not support images with alpha
+ for ( int y = 0 ; y < m_imgSource.Height() ; ++y )
+ {
+ for ( int x = 0 ; x < m_imgSource.Width() ; ++x )
+ {
+ Color c = m_imgSource.GetColor( x, y );
+ c[3] = 255;
+ m_imgSource.SetColor( x, y, c );
+ }
+ }
+
+ // Show/hide controls as appropriate
+ WriteSelectImagePageControls();
+
+ // Tick the palette entries right now, no matter what else happened
+ //OnTick();
+
+ // change the cursor back to normal
+ vgui::surface()->SetCursor( vgui::dc_user );
+}
+
+void CConfirmCustomizeTextureDialog::OnTextChanged( vgui::Panel *panel )
+{
+ // Check for known controls
+ if ( panel == m_pFilterCombo )
+ {
+
+ // Mark us as dirty
+ MarkFilteredImageDirty();
+
+ // Update controls
+ ShowFilterControls();
+ }
+ else if ( panel == m_pSquarizeCombo )
+ {
+
+ // If image is nearly square, ignore this, there shouldn't
+ // be any options
+ if ( !IsSourceImageSquare() )
+ {
+
+ // Set new option, if it is changing
+ bool bNewOption = ( m_pSquarizeCombo->GetActiveItem() == 1 );
+ if ( !bNewOption != !m_bCropToSquare )
+ {
+ m_bCropToSquare = bNewOption;
+ MarkSquareImageDirty();
+ }
+ }
+ }
+ else if ( panel == m_pStencilModeCombo )
+ {
+ MarkFilteredImageDirty();
+ }
+ else
+ {
+ // Who else is talking to us?
+ Assert( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_CustomizeTexture::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmCustomizeTextureDialog *dialog = vgui::SETUP_PANEL( new CConfirmCustomizeTextureDialog( pParent, pTool, pSubject ) );
+ MakeModalAndBringToFront( dialog );
+}
diff --git a/game/client/econ/tool_items/decoder_ring_tool.cpp b/game/client/econ/tool_items/decoder_ring_tool.cpp
new file mode 100644
index 0000000..f2f825d
--- /dev/null
+++ b/game/client/econ/tool_items/decoder_ring_tool.cpp
@@ -0,0 +1,213 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "vgui_controls/EditablePanel.h"
+#include "vgui_controls/TextEntry.h"
+#include "vgui/IInput.h"
+#include "econ_item_system.h"
+#include "econ_item_constants.h"
+#include "econ_gcmessages.h"
+#include "econ_item_inventory.h"
+#include "econ_item_tools.h"
+#include "tool_items.h"
+#include "decoder_ring_tool.h"
+#include "vgui/ISurface.h"
+#include "econ_controls.h"
+#include "econ_ui.h"
+#include "gc_clientsystem.h"
+#include "collection_crafting_panel.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+enum
+{
+ CRATETYPE_NORMAL = 0,
+ CRATETYPE_ROBO = 1,
+};
+
+#define ROBOCRATE_UNLOCKING_SND "/mvm/mvm_tank_deploy.wav"
+#define ROBOCRATE_OPENED_SND "/mvm/mvm_tank_explode.wav"
+
+static int s_iCrateType = CRATETYPE_NORMAL;
+
+//-----------------------------------------------------------------------------
+// Purpose: Confirm / abort tool application
+//-----------------------------------------------------------------------------
+class CConfirmDecodeDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmDecodeDialog, CBaseToolUsageDialog );
+
+public:
+ CConfirmDecodeDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *scheme );
+ virtual void Apply( void );
+ virtual void OnCommand( const char *command );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CConfirmDecodeDialog::CConfirmDecodeDialog( vgui::Panel *parent, CEconItemView *pTool, CEconItemView *pToolSubject ) : CBaseToolUsageDialog( parent, "ConfirmApplyDecodeDialog", pTool, pToolSubject )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmDecodeDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ LoadControlSettings( "Resource/UI/econ/ConfirmApplyDecodeDialog.res" );
+
+ const CEconItemView* pCrate = m_pSubjectModelPanel->GetItem();
+
+ s_iCrateType = CRATETYPE_NORMAL;
+ // Check Crate Type
+ if ( pCrate )
+ {
+ if ( pCrate->GetItemDefIndex() == 5635 ) // Robo Crate items_tools_crafting
+ {
+ s_iCrateType = CRATETYPE_ROBO;
+ }
+ }
+
+ if ( V_strstr( pCrate->GetItemDefinition()->GetDefinitionName(), "Case" ) )
+ {
+ SetDialogVariable( "confirm_text", GLocalizationProvider()->Find("#ToolDecodeConfirmCase") );
+ }
+ else
+ {
+ SetDialogVariable( "confirm_text", GLocalizationProvider()->Find("#ToolDecodeConfirm") );
+ }
+
+ const locchar_t *loc_Append = NULL;
+ if ( pCrate && pCrate->GetItemDefinition() && pCrate->GetItemDefinition()->GetHolidayRestriction() )
+ {
+ loc_Append = GLocalizationProvider()->Find( "#ToolDecodeConfirm_OptionalAppend_RestrictedContents" );
+ }
+ if ( !loc_Append )
+ {
+ loc_Append = LOCCHAR( "" );
+ }
+
+ SetDialogVariable( "optional_append", loc_Append );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmDecodeDialog::OnCommand( const char *command )
+{
+ if ( FStrEq( "cancel", command ) )
+ {
+ InventoryManager()->ShowItemsPickedUp( true );
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmDecodeDialog::Apply( void )
+{
+ static CSchemaAttributeDefHandle pAttrDef_SupplyCrateSeries( "set supply crate series" );
+
+ // Tell the GC to unlock the subject item.
+ GCSDK::CGCMsg< MsgGCUnlockCrate_t > msg( k_EMsgGCUnlockCrate );
+
+ msg.Body().m_unToolItemID = m_pToolModelPanel->GetItem()->GetItemID();
+ msg.Body().m_unSubjectItemID = m_pSubjectModelPanel->GetItem()->GetItemID();
+
+ int iSeries = 0;
+ CEconItemView* pCrate = m_pSubjectModelPanel->GetItem();
+ if ( pCrate )
+ {
+ float fSeries;
+ if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pCrate, pAttrDef_SupplyCrateSeries, &fSeries ) )
+ {
+ iSeries = fSeries;
+ }
+ }
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "unlocked_supply_crate", iSeries );
+
+ GCClientSystem()->BSendMessage( msg );
+
+ CCollectionCraftingPanel *pPanel = EconUI()->GetBackpackPanel()->GetCollectionCraftPanel();
+ if ( pPanel )
+ {
+ pPanel->SetWaitingForItem( kEconItemOrigin_FoundInCrate );
+ }
+}
+
+
+void CEconTool_CrateKey::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmDecodeDialog *dialog = vgui::SETUP_PANEL( new CConfirmDecodeDialog( pParent, pTool, pSubject ) );
+ MakeModalAndBringToFront( dialog );
+}
+
+
+class CWaitForCrateDialog : public CGenericWaitingDialog
+{
+public:
+ CWaitForCrateDialog( vgui::Panel *pParent ) : CGenericWaitingDialog( pParent )
+ {
+ }
+
+protected:
+ virtual void OnTimeout()
+ {
+ // Play an exciting sound!
+ if ( s_iCrateType == CRATETYPE_ROBO )
+ {
+ vgui::surface()->PlaySound( ROBOCRATE_OPENED_SND );
+ }
+ else
+ {
+ vgui::surface()->PlaySound( "misc/achievement_earned.wav" );
+ }
+
+
+ // Show them their loot!
+ InventoryManager()->ShowItemsPickedUp( true );
+ }
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: GC Msg handler to receive the decoder ring response
+//-----------------------------------------------------------------------------
+class CGCUnlockCrateResponse : public GCSDK::CGCClientJob
+{
+public:
+ CGCUnlockCrateResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket );
+
+ if ( s_iCrateType == CRATETYPE_ROBO )
+ {
+ vgui::surface()->PlaySound( ROBOCRATE_UNLOCKING_SND );
+ }
+ else
+ {
+ vgui::surface()->PlaySound( "ui/item_open_crate_short.wav" );
+ }
+
+ return true;
+ }
+
+};
+
+GC_REG_JOB( GCSDK::CGCClient, CGCUnlockCrateResponse, "CGCUnlockCrateResponse", k_EMsgGCUnlockCrateResponse, GCSDK::k_EServerTypeGCClient );
diff --git a/game/client/econ/tool_items/decoder_ring_tool.h b/game/client/econ/tool_items/decoder_ring_tool.h
new file mode 100644
index 0000000..3228521
--- /dev/null
+++ b/game/client/econ/tool_items/decoder_ring_tool.h
@@ -0,0 +1,14 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef DECODER_RING_TOOL_H
+#define DECODER_RING_TOOL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#endif // DECODER_RING_TOOL_H
diff --git a/game/client/econ/tool_items/gift_wrap_tool.cpp b/game/client/econ/tool_items/gift_wrap_tool.cpp
new file mode 100644
index 0000000..c8c5c1d
--- /dev/null
+++ b/game/client/econ/tool_items/gift_wrap_tool.cpp
@@ -0,0 +1,241 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "vgui_controls/EditablePanel.h"
+#include "vgui_controls/TextEntry.h"
+#include "vgui/IInput.h"
+#include "econ_item_system.h"
+#include "econ_item_constants.h"
+#include "econ_item_tools.h"
+#include "econ_gcmessages.h"
+#include "econ_item_inventory.h"
+#include "tool_items.h"
+#include "gift_wrap_tool.h"
+#include "econ_ui.h"
+#include "vgui/ISurface.h"
+#include "econ_controls.h"
+#include "confirm_dialog.h"
+#include "gc_clientsystem.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+// Purpose: Confirm / abort tool application
+//-----------------------------------------------------------------------------
+class CConfirmGiftWrapDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmGiftWrapDialog, CBaseToolUsageDialog );
+
+public:
+ CConfirmGiftWrapDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *scheme );
+ virtual void Apply( void );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Completed wrapping dialog
+//-----------------------------------------------------------------------------
+class CWaitForGiftWrapDialog : public CGenericWaitingDialog
+{
+public:
+ CWaitForGiftWrapDialog( vgui::Panel *pParent ) : CGenericWaitingDialog( pParent )
+ {
+ }
+
+protected:
+ virtual void OnTimeout()
+ {
+ // Play an exciting sound!
+ vgui::surface()->PlaySound( "misc/achievement_earned.wav" );
+
+ // Show them the result item.
+ InventoryManager()->ShowItemsPickedUp( true );
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CConfirmGiftWrapDialog::CConfirmGiftWrapDialog( vgui::Panel *parent, CEconItemView *pTool, CEconItemView *pToolSubject ) : CBaseToolUsageDialog( parent, "ConfirmApplyGiftWrapDialog", pTool, pToolSubject )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmGiftWrapDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ LoadControlSettings( "Resource/UI/econ/ConfirmApplyGiftWrapDialog.res" );
+
+ // We might want to change our label text to explicitly call out that we'll reset strange scores
+ // on gift wrap, but only if we're trying to use this gift wrap on a strange item that has scores
+ // that would be affected by it.
+ CEconItemView *pSubjectItemView = GetSubjectItem();
+ CExLabel *pTextLabel = dynamic_cast<CExLabel *>( FindChildByName( "ConfirmLabel" ) );
+ CExLabel *pTextLabelStrange = dynamic_cast<CExLabel *>( FindChildByName( "ConfirmLabelStrange" ) );
+
+ if ( pSubjectItemView && pTextLabel && pTextLabelStrange )
+ {
+ bool bHasNonZeroScore = false;
+
+ for ( int i = 0; i < GetKillEaterAttrCount(); i++ )
+ {
+ uint32 unScore;
+ if ( pSubjectItemView->FindAttribute( GetKillEaterAttr_Score( i ), &unScore ) && unScore > 0 )
+ {
+ bHasNonZeroScore = true;
+ break;
+ }
+ }
+
+ if ( bHasNonZeroScore )
+ {
+ pTextLabel->SetVisible( false );
+ pTextLabelStrange->SetVisible( true );
+ }
+ }
+
+ BaseClass::ApplySchemeSettings( pScheme );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmGiftWrapDialog::Apply( void )
+{
+ // Tell the GC to wrap the subject item.
+ GCSDK::CGCMsg< MsgGCGiftWrapItem_t > msg( k_EMsgGCGiftWrapItem );
+
+ msg.Body().m_unToolItemID = m_pToolModelPanel->GetItem()->GetItemID();
+ msg.Body().m_unSubjectItemID = m_pSubjectModelPanel->GetItem()->GetItemID();
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pSubjectModelPanel->GetItem(), "gift_wrap_item" );
+
+ GCClientSystem()->BSendMessage( msg );
+
+ vgui::surface()->PlaySound( "ui/item_gift_wrap_use.wav" );
+ ShowWaitingDialog( new CWaitForGiftWrapDialog( NULL ), "#ToolGiftWrapInProgress", true, false, 5.0f );
+}
+
+// Entry point from the UI.
+void CEconTool_GiftWrap::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmGiftWrapDialog *dialog = vgui::SETUP_PANEL( new CConfirmGiftWrapDialog( pParent, pTool, pSubject ) );
+ MakeModalAndBringToFront( dialog );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: GC Msg handler to receive the server response that we've given an item.
+//-----------------------------------------------------------------------------
+class CGCGiftGivenResponse : public GCSDK::CGCClientJob
+{
+public:
+ CGCGiftGivenResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CProtoBufMsg<CMsgDeliverGiftResponseGiver> msg( pNetPacket );
+
+ // Pop up a notification to confirm that the gift has been sent.
+ switch ( msg.Body().response_code() )
+ {
+ case k_EGCMsgResponseOK:
+ if ( msg.Body().has_receiver_account_name() )
+ {
+ KeyValues *pkv = new KeyValues( "GiftReceiverParams" );
+ KeyValuesAD kvad( pkv );
+
+ pkv->SetString( "receiver_account_name", msg.Body().receiver_account_name().c_str() );
+ ShowMessageBox( "#TF_DeliverGiftResultDialog_Title", "#TF_DeliverGiftResultDialog_Success_WithAccount", pkv, "#GameUI_OK" );
+ }
+ else
+ {
+ ShowMessageBox( "#TF_DeliverGiftResultDialog_Title", "#TF_DeliverGiftResultDialog_Success", "#GameUI_OK" );
+ }
+ break;
+ case k_EGCMsgResponseDenied:
+ ShowMessageBox( "#TF_DeliverGiftResultDialog_Title", "#TF_DeliverGiftResultDialog_VAC", "#GameUI_OK" );
+ break;
+ default:
+ ShowMessageBox( "#TF_DeliverGiftResultDialog_Title", "#TF_DeliverGiftResultDialog_Fail", "#GameUI_OK" );
+ break;
+ } // switch
+
+ return true;
+ }
+};
+GC_REG_JOB( GCSDK::CGCClient, CGCGiftGivenResponse, "CGCGiftGivenResponse", k_EMsgGCDeliverGiftResponseGiver, GCSDK::k_EServerTypeGCClient );
+
+//-----------------------------------------------------------------------------
+// Purpose: GC Msg handler to receive the server response that we've received an item.
+//-----------------------------------------------------------------------------
+class CGCGiftReceivedResponse : public GCSDK::CGCClientJob
+{
+public:
+ CGCGiftReceivedResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket );
+
+ // If the receiver is online when the gift is sent, they will get this response.
+ InventoryManager()->GetLocalInventory()->NotifyHasNewItems();
+
+ return true;
+ }
+};
+GC_REG_JOB( GCSDK::CGCClient, CGCGiftReceivedResponse, "CGCGiftReceivedResponse", k_EMsgGCDeliverGiftResponseReceiver, GCSDK::k_EServerTypeGCClient );
+
+//-----------------------------------------------------------------------------
+// Purpose: Completed unwrapping...
+//-----------------------------------------------------------------------------
+class CWaitForGiftUnwrapDialog : public CGenericWaitingDialog
+{
+public:
+ CWaitForGiftUnwrapDialog( vgui::Panel *pParent ) : CGenericWaitingDialog( pParent )
+ {
+ }
+
+protected:
+ virtual void OnTimeout()
+ {
+ // Play an exciting sound!
+ vgui::surface()->PlaySound( "misc/achievement_earned.wav" );
+
+ // Show them the result item.
+ InventoryManager()->ShowItemsPickedUp( true );
+ }
+};
+
+static void UnwrapGiftConfirm( bool bConfirmed, void *pContext )
+{
+ if ( bConfirmed )
+ {
+ vgui::surface()->PlaySound( "ui/item_gift_wrap_unwrap.wav" );
+ ShowWaitingDialog( new CWaitForGiftWrapDialog( NULL ), "#ToolGiftUnwrapInProgress", true, false, 5.0f );
+
+ CEconItemView *pItem = (CEconItemView*) pContext;
+ GCSDK::CGCMsg< MsgGCUnwrapGiftRequest_t > msg( k_EMsgGCUnwrapGiftRequest );
+ msg.Body().m_unItemID = pItem->GetItemID();
+ GCClientSystem()->BSendMessage( msg );
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, pItem, "unwrapped_gift" );
+ }
+}
+
+void PerformToolAction_UnwrapGift( vgui::Panel* pParent, CEconItemView *pGiftItem )
+{
+ CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#TF_UnwrapGift_Title", "#TF_UnwrapGift_Text",
+ "#GameUI_OK", "#Cancel",
+ &UnwrapGiftConfirm );
+ pDialog->AddStringToken( "item_name", pGiftItem->GetItemName() );
+ pDialog->SetContext( pGiftItem );
+}
diff --git a/game/client/econ/tool_items/gift_wrap_tool.h b/game/client/econ/tool_items/gift_wrap_tool.h
new file mode 100644
index 0000000..61f54e3
--- /dev/null
+++ b/game/client/econ/tool_items/gift_wrap_tool.h
@@ -0,0 +1,14 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef GIFT_WRAP_TOOL_H
+#define GIFT_WRAP_TOOL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#endif // GIFT_WRAP_TOOL_H
diff --git a/game/client/econ/tool_items/paint_can_tool.cpp b/game/client/econ/tool_items/paint_can_tool.cpp
new file mode 100644
index 0000000..3570c47
--- /dev/null
+++ b/game/client/econ/tool_items/paint_can_tool.cpp
@@ -0,0 +1,205 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "vgui_controls/EditablePanel.h"
+#include "vgui_controls/TextEntry.h"
+#include "vgui/IInput.h"
+#include "econ_item_system.h"
+#include "econ_item_constants.h"
+#include "econ_gcmessages.h"
+#include "econ_item_inventory.h"
+#include "econ_item_tools.h"
+#include "tool_items.h"
+#include "paint_can_tool.h"
+#include "econ_ui.h"
+#include "gc_clientsystem.h"
+
+#ifdef TF_CLIENT_DLL
+#include "tf_shareddefs.h"
+#endif // TF_CLIENT_DLL
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+enum
+{
+#ifdef TF_CLIENT_DLL
+ kTeamID0 = TF_TEAM_RED,
+ kTeamID1 = TF_TEAM_BLUE,
+#else // !defined( TF_CLIENT_DLL )
+ kTeamID0 = -1,
+ kTeamID1 = -1,
+#endif // TF_CLIENT_DLL
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Confirm / abort paint application
+//-----------------------------------------------------------------------------
+class CConfirmApplyPaintCanBaseDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmApplyPaintCanBaseDialog, CBaseToolUsageDialog );
+
+protected:
+ CConfirmApplyPaintCanBaseDialog( vgui::Panel *pParent, const char *szType, CEconItemView *pTool, CEconItemView *pToolSubject )
+ : CBaseToolUsageDialog( pParent, szType, pTool, pToolSubject )
+ {
+ //
+ }
+
+public:
+ virtual void Apply( void )
+ {
+ // Send the apply request to the GC
+ GCSDK::CGCMsg< MsgGCPaintItem_t > msg( k_EMsgGCPaintItem );
+
+ msg.Body().m_unToolItemID = m_pToolModelPanel->GetItem()->GetItemID();
+ msg.Body().m_unSubjectItemID = m_pSubjectModelPanel->GetItem()->GetItemID();
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "painted_item" );
+
+ GCClientSystem()->BSendMessage( msg );
+ }
+
+protected:
+ // Set up a particular panel for a certain item with a certain preview color.
+ static void SetupPaintModelPanel( CItemModelPanel *pPaintModelPanel, CEconItemView *pToolSubjectView, int iTeam, int iPaintRGB )
+ {
+ static CSchemaAttributeDefHandle pAttrDef_ItemTintRGB( "set item tint RGB" );
+
+ if ( !pAttrDef_ItemTintRGB )
+ return;
+
+ // Fake-paint the demonstration items.
+ pPaintModelPanel->SetItem( pToolSubjectView );
+ pPaintModelPanel->GetItem()->GetAttributeList()->SetRuntimeAttributeValue( pAttrDef_ItemTintRGB, iPaintRGB );
+
+ // Set the appropriate skins for each item given the team and the style selected.
+ int iStyleSkin = pToolSubjectView->GetSkin( iTeam );
+
+ if ( iStyleSkin == -1 )
+ {
+ // Fallback case: rely on default skins.
+ pPaintModelPanel->SetSkin( iTeam == kTeamID0 ? 0 : 1 );
+ }
+ else
+ {
+ pPaintModelPanel->SetSkin( iStyleSkin );
+ }
+
+ pPaintModelPanel->SetActAsButton( true, false );
+ pPaintModelPanel->GetItem()->InvalidateColor();
+ pPaintModelPanel->GetItem()->InvalidateOverrideColor();
+ }
+
+ CItemModelPanel *m_pPaintModelPanel;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Preview a single-color paint
+//-----------------------------------------------------------------------------
+class CConfirmApplyPaintCanDialog : public CConfirmApplyPaintCanBaseDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmApplyPaintCanDialog, CConfirmApplyPaintCanBaseDialog );
+
+public:
+ CConfirmApplyPaintCanDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject )
+ : CConfirmApplyPaintCanBaseDialog( pParent, "ConfirmApplyPaintCanDialog", pTool, pToolSubject )
+ {
+ m_pPaintModelPanel = new CItemModelPanel( this, "paint_model" );
+ m_pPaintModelPanel->SetItem( pToolSubject );
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ LoadControlSettings( "Resource/UI/econ/ConfirmApplyPaintCanDialog.res" );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ SetupPaintModelPanel( m_pPaintModelPanel, m_pSubjectModelPanel->GetItem(), kTeamID0, m_pToolModelPanel->GetItem()->GetModifiedRGBValue( false ) );
+ }
+
+private:
+ CItemModelPanel *m_pPaintModelPanel;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Preview team-colored paint
+//-----------------------------------------------------------------------------
+class CConfirmApplyTeamColorPaintCanDialog : public CConfirmApplyPaintCanBaseDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmApplyTeamColorPaintCanDialog, CConfirmApplyPaintCanBaseDialog );
+
+public:
+ CConfirmApplyTeamColorPaintCanDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject )
+ : CConfirmApplyPaintCanBaseDialog( pParent, "ConfirmApplyTeamColorPaintCanDialog", pTool, pToolSubject )
+ {
+ m_pPaintModelPanel_Red = new CItemModelPanel( this, "paint_model_red" );
+ m_pPaintModelPanel_Red->SetItem( pToolSubject );
+ m_pPaintModelPanel_Blue = new CItemModelPanel( this, "paint_model_blue" );
+ m_pPaintModelPanel_Blue->SetItem( pToolSubject );
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ LoadControlSettings( "Resource/UI/econ/ConfirmApplyTeamColorPaintCanDialog.res" );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ SetupPaintModelPanel( m_pPaintModelPanel_Red, m_pSubjectModelPanel->GetItem(), kTeamID0, m_pToolModelPanel->GetItem()->GetModifiedRGBValue( false ) );
+ SetupPaintModelPanel( m_pPaintModelPanel_Blue, m_pSubjectModelPanel->GetItem(), kTeamID1, m_pToolModelPanel->GetItem()->GetModifiedRGBValue( true ) );
+
+ // @note Tom Bui: we need to change the global index so that the material does not get
+ // re-used for each item. Should be ok to change the high bit.
+ Assert( m_pPaintModelPanel_Red->GetItem() );
+ m_pPaintModelPanel_Red->GetItem()->SetItemID( m_pPaintModelPanel_Red->GetItem()->GetItemID() | ( 1LL << 63 ) );
+ }
+
+private:
+ CItemModelPanel *m_pPaintModelPanel_Red;
+ CItemModelPanel *m_pPaintModelPanel_Blue;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_PaintCan::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ // Bizarro logic: we have no way of finding out whether an item has two different
+ // colors of paint applied so instead we ask whether both colors are
+ // the same.
+ CBaseToolUsageDialog *dialog = NULL;
+ if ( pTool->GetModifiedRGBValue( true ) != pTool->GetModifiedRGBValue( false ) )
+ {
+ dialog = new CConfirmApplyTeamColorPaintCanDialog( pParent, pTool, pSubject );
+ }
+ else
+ {
+ dialog = new CConfirmApplyPaintCanDialog( pParent, pTool, pSubject );
+ }
+ vgui::SETUP_PANEL( dialog );
+ MakeModalAndBringToFront( dialog );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: GC Msg handler to receive the paint can response
+//-----------------------------------------------------------------------------
+class CGCPaintItemResponse : public GCSDK::CGCClientJob
+{
+public:
+ CGCPaintItemResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket );
+ InventoryManager()->ShowItemsPickedUp( true );
+ return true;
+ }
+
+};
+
+GC_REG_JOB( GCSDK::CGCClient, CGCPaintItemResponse, "CGCPaintItemResponse", k_EMsgGCPaintItemResponse, GCSDK::k_EServerTypeGCClient );
diff --git a/game/client/econ/tool_items/paint_can_tool.h b/game/client/econ/tool_items/paint_can_tool.h
new file mode 100644
index 0000000..36fa9de
--- /dev/null
+++ b/game/client/econ/tool_items/paint_can_tool.h
@@ -0,0 +1,15 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef PAINT_CAN_TOOL_H
+#define PAINT_CAN_TOOL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#endif // PAINT_CAN_TOOL_H
diff --git a/game/client/econ/tool_items/rename_tool_ui.cpp b/game/client/econ/tool_items/rename_tool_ui.cpp
new file mode 100644
index 0000000..af76dc0
--- /dev/null
+++ b/game/client/econ/tool_items/rename_tool_ui.cpp
@@ -0,0 +1,306 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "vgui_controls/EditablePanel.h"
+#include "vgui_controls/TextEntry.h"
+#include "vgui/IInput.h"
+#include "vgui//ILocalize.h"
+#include "econ_item_system.h"
+#include "econ_item_constants.h"
+#include "econ_gcmessages.h"
+#include "econ_item_inventory.h"
+#include "econ_item_tools.h"
+#include "tool_items.h"
+#include "rename_tool_ui.h"
+#include "econ_ui.h"
+#include "gc_clientsystem.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+CNameToolUsageDialog::CNameToolUsageDialog( vgui::Panel *pParent, const char* pszName, CEconItemView *pTool, CEconItemView *pToolSubject, bool bDescription )
+: CBaseToolUsageDialog( pParent, pszName, pTool, pToolSubject )
+{
+ m_bDescription = bDescription;
+}
+
+int CNameToolUsageDialog::GetMaxLength()
+{
+ if ( m_bDescription )
+ return MAX_ITEM_CUSTOM_DESC_LENGTH;
+ else
+ return MAX_ITEM_CUSTOM_NAME_LENGTH;
+}
+
+int CNameToolUsageDialog::GetMaxDBSize()
+{
+ if ( m_bDescription )
+ return MAX_ITEM_CUSTOM_DESC_DATABASE_SIZE;
+ else
+ return MAX_ITEM_CUSTOM_NAME_DATABASE_SIZE;
+}
+
+void CEconTool_NameTag::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CRequestNameDialog *dialog = vgui::SETUP_PANEL( new CRequestNameDialog( pParent, "ItemRenameDialog", pTool, pSubject, false ) );
+ MakeModalAndBringToFront( dialog );
+}
+
+//-----------------------------------------------------------------------------
+CRequestNameDialog::CRequestNameDialog( vgui::Panel *parent, const char* pszName, CEconItemView *pTool, CEconItemView *pToolSubject, bool bDescription ) :
+ CNameToolUsageDialog( parent, pszName, pTool, pToolSubject, bDescription )
+{
+ m_pCustomNameEntry = new vgui::TextEntry( this, "CustomNameEntry" );
+ m_bDescription = bDescription;
+}
+
+//-----------------------------------------------------------------------------
+void CRequestNameDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ LoadControlSettings( "resource/UI/ItemRenameDialog.res" );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_pOldNameLabel = dynamic_cast<vgui::Label *>( FindChildByName( "OldItemNameDescLabel" ) );
+ if ( m_pOldNameLabel )
+ {
+ if ( m_bDescription )
+ m_pOldNameLabel->SetText( g_pVGuiLocalize->Find( "#ToolItemRenameOldItemDesc" ) );
+ else
+ m_pOldNameLabel->SetText( g_pVGuiLocalize->Find( "#ToolItemRenameOldItemName" ) );
+ }
+
+ m_pNewNameLabel = dynamic_cast<vgui::Label *>( FindChildByName( "NewItemNameDescLabel" ) );
+ if ( m_pNewNameLabel )
+ {
+ if ( m_bDescription )
+ m_pNewNameLabel->SetText( g_pVGuiLocalize->Find( "#ToolItemRenameNewItemDesc" ) );
+ else
+ m_pNewNameLabel->SetText( g_pVGuiLocalize->Find( "#ToolItemRenameNewItemName" ) );
+ }
+
+ m_pOldName = dynamic_cast<vgui::Label *>( FindChildByName( "OldItemNameLabel" ) );
+ if ( m_pOldName )
+ {
+ if ( m_bDescription )
+ {
+ CEconItem *pSOCData = m_pSubjectModelPanel->GetItem()->GetSOCData();
+ if ( pSOCData && pSOCData->GetCustomDesc() )
+ m_pOldName->SetText( pSOCData->GetCustomDesc() );
+ else
+ m_pOldName->SetText( m_pSubjectModelPanel->GetItem()->GetStaticData()->GetItemDesc() );
+ }
+ else
+ {
+ CEconItem *pSOCData = m_pSubjectModelPanel->GetItem()->GetSOCData();
+ if ( pSOCData && pSOCData->GetCustomName() )
+ m_pOldName->SetText( pSOCData->GetCustomName() );
+ else
+ m_pOldName->SetText( m_pSubjectModelPanel->GetItem()->GetStaticData()->GetItemBaseName() );
+ }
+ }
+
+ CExButton *pOKButton = dynamic_cast< CExButton* >( FindChildByName( "OkButton" ) );
+ if ( pOKButton )
+ {
+ if ( m_bDescription )
+ pOKButton->SetText( "#CraftDescribeOk" );
+ }
+
+ m_pCustomNameEntry->SetMaximumCharCount( GetMaxLength() );
+ m_pCustomNameEntry->SetAllowNonAsciiCharacters( true );
+}
+
+
+//-----------------------------------------------------------------------------
+void CRequestNameDialog::MoveToFront()
+{
+ BaseClass::MoveToFront();
+
+ // do this after MoveToFront so we can force the text box to have focus instead
+ // of the dialog itself
+ m_pCustomNameEntry->RequestFocus();
+}
+
+
+//-----------------------------------------------------------------------------
+void CRequestNameDialog::Apply( void )
+{
+ const int maxNameLength = MAX_ITEM_CUSTOM_DESC_LENGTH + 1;
+ wchar_t inputName[ maxNameLength ];
+
+ m_pCustomNameEntry->GetText( inputName, sizeof(inputName) );
+
+ // pop up modal confirmation dialog
+ CConfirmNameDialog *dialog = vgui::SETUP_PANEL( new CConfirmNameDialog( GetParent(), "ItemRenameConfirmationDialog", m_pToolModelPanel->GetItem(), m_pSubjectModelPanel->GetItem(), inputName, m_bDescription ) );
+ MakeModalAndBringToFront( dialog );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Gives focus back to the name entry field after the mouse enters a
+// item model panel
+//-----------------------------------------------------------------------------
+void CRequestNameDialog::OnItemPanelEntered( vgui::Panel *panel )
+{
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+
+ if ( pItemPanel && IsVisible() )
+ {
+ // The item panel is going to try and steal our focus. Steal it back!
+ m_pCustomNameEntry->RequestFocus();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+CConfirmNameDialog::CConfirmNameDialog( vgui::Panel *parent, const char* pszName, CEconItemView *pTool, CEconItemView *pToolSubject, const wchar_t *name, bool bDescription ) :
+ CNameToolUsageDialog( parent, pszName, pTool, pToolSubject, bDescription )
+{
+ Q_wcsncpy( m_name, name, sizeof(m_name) );
+ m_bDescription = bDescription;
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// We're going to want to flesh this out to trim off leading/training spaces, etc
+//
+bool CConfirmNameDialog::IsNameValid( void ) const
+{
+ // legal names are 1 or more alphanumeric values (only)
+ const wchar_t *c = m_name;
+ int length = 0;
+ while( *c )
+ {
+ // no leading spaces
+ if ( length == 0 && *c == ' ' )
+ return false;
+
+ ++c;
+ ++length;
+ }
+
+ // no trailing spaces
+ if ( length > 0 && m_name[length-1] == ' ' )
+ return false;
+
+ return (length > 0);
+}
+
+
+//-----------------------------------------------------------------------------
+void CConfirmNameDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ if ( !IsNameValid() )
+ {
+ // pop up bad name dialog
+ LoadControlSettings( "resource/UI/ItemRenameInvalidDialog.res" );
+ }
+ else
+ {
+ // pop up "are you sure" dialog
+ LoadControlSettings( "resource/UI/ItemRenameConfirmationDialog.res" );
+ }
+
+ // Set our dialog name, but pre & post pend it with quotes
+ wchar_t tmpname[ MAX_ITEM_CUSTOM_DESC_LENGTH+3 ];
+ V_wcscpy_safe( tmpname, L"\"" );
+ V_wcscat_safe( tmpname, m_name );
+ V_wcscat_safe( tmpname, L"\"" );
+ SetDialogVariable( "name", tmpname );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+}
+
+
+//-----------------------------------------------------------------------------
+void CConfirmNameDialog::Apply( void )
+{
+ // the GC stores 8-bit chars, so convert Unicode name to UTF8
+ char* utf8Name = new char[ GetMaxDBSize() ];
+ int count = V_UnicodeToUTF8( m_name, utf8Name, GetMaxDBSize() );
+
+ if ( count > GetMaxDBSize() )
+ {
+ // the encoded name exceeds the GC's storage limit
+ return;
+ }
+
+ if ( m_pSubjectModelPanel->GetItem()->GetItemID() != INVALID_ITEM_ID )
+ {
+ // Name has been confirmed - send message to GC to apply name to item
+ GCSDK::CGCMsg< MsgGCNameItem_t > msg( k_EMsgGCNameItem );
+
+ msg.Body().m_unToolItemID = m_pToolModelPanel->GetItem()->GetItemID();
+ msg.Body().m_unSubjectItemID = m_pSubjectModelPanel->GetItem()->GetItemID();
+ msg.AddStrData( utf8Name );
+ GCClientSystem()->BSendMessage( msg );
+ }
+ else
+ {
+ // Name has been confirmed - send message to GC to apply name to item
+ GCSDK::CGCMsg< MsgGCNameBaseItem_t > msg( k_EMsgGCNameBaseItem );
+
+ msg.Body().m_unToolItemID = m_pToolModelPanel->GetItem()->GetItemID();
+ msg.Body().m_unBaseItemDefinitionID = m_pSubjectModelPanel->GetItem()->GetStaticData()->GetDefinitionIndex();
+ msg.AddStrData( utf8Name );
+ GCClientSystem()->BSendMessage( msg );
+ }
+
+ if ( m_bDescription )
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "redescription_item" );
+ else
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "renamed_item" );
+
+ delete []utf8Name;
+}
+
+//-----------------------------------------------------------------------------
+void CConfirmNameDialog::OnCommand( const char *command )
+{
+ BaseClass::OnCommand( command );
+
+ if ( !Q_stricmp( command, "backfrominvalid" ) )
+ {
+ // Re-open the name dialog
+ CRequestNameDialog *dialog = vgui::SETUP_PANEL( new CRequestNameDialog( GetParent(), "ItemRenameDialog", m_pToolModelPanel->GetItem(), m_pSubjectModelPanel->GetItem(), m_bDescription ) );
+ MakeModalAndBringToFront( dialog );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: GC Msg handler to receive the name base item response
+//-----------------------------------------------------------------------------
+class CGCNameBaseItemResponse : public GCSDK::CGCClientJob
+{
+public:
+ CGCNameBaseItemResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket );
+ InventoryManager()->ShowItemsPickedUp( true );
+ return true;
+ }
+
+};
+
+GC_REG_JOB( GCSDK::CGCClient, CGCNameBaseItemResponse, "CGCNameBaseItemResponse", k_EMsgGCNameBaseItemResponse, GCSDK::k_EServerTypeGCClient );
+
+
+//-----------------------------------------------------------------------------
+// Purpose: UI Hook for applying a new description to items.
+//-----------------------------------------------------------------------------
+void CEconTool_DescTag::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CRequestNameDialog *dialog = vgui::SETUP_PANEL( new CRequestNameDialog( pParent, "ItemRenameDialog", pTool, pSubject, true ) );
+ MakeModalAndBringToFront( dialog );
+} \ No newline at end of file
diff --git a/game/client/econ/tool_items/rename_tool_ui.h b/game/client/econ/tool_items/rename_tool_ui.h
new file mode 100644
index 0000000..c65c09c
--- /dev/null
+++ b/game/client/econ/tool_items/rename_tool_ui.h
@@ -0,0 +1,74 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef RENAME_TOOL_UI_H
+#define RENAME_TOOL_UI_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tool_items.h"
+
+class CNameToolUsageDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CNameToolUsageDialog, CBaseToolUsageDialog );
+
+public:
+ CNameToolUsageDialog( vgui::Panel *pParent, const char* pszName, CEconItemView *pTool, CEconItemView *pToolSubject, bool bDescription );
+ virtual int GetMaxLength();
+ virtual int GetMaxDBSize();
+
+protected:
+ bool m_bDescription;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: A dialog used to input a Tool's name payload
+//-----------------------------------------------------------------------------
+class CRequestNameDialog : public CNameToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CRequestNameDialog, CNameToolUsageDialog );
+
+public:
+ CRequestNameDialog( vgui::Panel *pParent, const char* pszName, CEconItemView *pTool, CEconItemView *pToolSubject, bool bDescription );
+
+ virtual void MoveToFront();
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void Apply( void );
+
+ MESSAGE_FUNC_PTR( OnItemPanelEntered, "ItemPanelEntered", panel );
+
+private:
+ vgui::TextEntry *m_pCustomNameEntry;
+ vgui::Label *m_pOldNameLabel;
+ vgui::Label *m_pOldName;
+ vgui::Label *m_pNewNameLabel;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Confirm name and commit or reject
+//-----------------------------------------------------------------------------
+class CConfirmNameDialog : public CNameToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmNameDialog, CNameToolUsageDialog );
+
+public:
+ CConfirmNameDialog( vgui::Panel *pParent, const char* pszName, CEconItemView *pTool, CEconItemView *pToolSubject, const wchar_t *name, bool bDescription );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *scheme );
+ virtual void Apply( void );
+ virtual void OnCommand( const char *command );
+
+private:
+ wchar_t m_name[ MAX_ITEM_CUSTOM_DESC_LENGTH+1 ];
+
+ bool IsNameValid( void ) const;
+};
+
+
+#endif // RENAME_TOOL_UI_H
diff --git a/game/client/econ/tool_items/tool_items.cpp b/game/client/econ/tool_items/tool_items.cpp
new file mode 100644
index 0000000..2fbed7f
--- /dev/null
+++ b/game/client/econ/tool_items/tool_items.cpp
@@ -0,0 +1,968 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "vgui_controls/EditablePanel.h"
+#include "vgui_controls/TextEntry.h"
+#include "vgui_controls/TextImage.h"
+#include "vgui/IInput.h"
+#include "econ_item_system.h"
+#include "econ_item_constants.h"
+#include "econ_gcmessages.h"
+#include "econ_ui.h"
+#include "tool_items.h"
+#ifdef TF_CLIENT_DLL
+ #include "tf_item_tools.h"
+#else
+ #include "econ_item_tools.h"
+#endif
+#include <vgui/ILocalize.h>
+#include "gc_clientsystem.h"
+#include "item_style_select_dialog.h" // for CComboBoxBackpackOverlayDialogBase
+#include "backpack_panel.h"
+#include "vgui_controls/Controls.h"
+#include "vgui/ISurface.h"
+
+#ifdef TF_CLIENT_DLL
+#include "character_info_panel.h"
+#include "c_tf_gamestats.h"
+#endif
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseToolUsageDialog::CBaseToolUsageDialog( vgui::Panel *parent, const char *panelName, CEconItemView *pTool, CEconItemView *pToolSubject ) : vgui::EditablePanel( parent, panelName )
+{
+ m_pToolModelPanel = new CItemModelPanel( this, "tool_modelpanel" );
+ m_pToolModelPanel->SetActAsButton( true, true );
+ m_pSubjectModelPanel = new CItemModelPanel( this, "subject_modelpanel" );
+ m_pSubjectModelPanel->SetActAsButton( true, true );
+ m_pMouseOverItemPanel = vgui::SETUP_PANEL( new CItemModelPanel( this, "mouseoveritempanel" ) );
+
+ m_pMouseOverTooltip = new CItemModelPanelToolTip( this );
+ m_pMouseOverTooltip->SetupPanels( this, m_pMouseOverItemPanel );
+ m_pToolModelPanel->SetTooltip( m_pMouseOverTooltip, "" );
+ m_pSubjectModelPanel->SetTooltip( m_pMouseOverTooltip, "" );
+
+ m_pToolModelPanel->SetItem( pTool );
+ m_pToolModelPanel->SetShowEquipped( true );
+ m_pSubjectModelPanel->SetItem( pToolSubject );
+ m_pSubjectModelPanel->SetShowEquipped( true );
+
+ m_pTitleLabel = NULL;
+ m_pszInternalPanelName = panelName ? panelName : "unknown";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseToolUsageDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ SetDialogVariable( "oldname", m_pSubjectModelPanel->GetItem()->GetItemName() );
+
+ m_pTitleLabel = dynamic_cast<vgui::Label*>( FindChildByName("TitleLabel") );
+ if ( m_pTitleLabel )
+ {
+ wchar_t *pszBaseString = g_pVGuiLocalize->Find( "ToolDialogTitle" );
+ if ( pszBaseString )
+ {
+ wchar_t wTemp[256];
+ g_pVGuiLocalize->ConstructString_safe( wTemp, pszBaseString, 2, m_pToolModelPanel->GetItem()->GetItemName(), m_pSubjectModelPanel->GetItem()->GetItemName() );
+ m_pTitleLabel->SetText( wTemp );
+
+ // Now go through the string and find the escape characters telling us where the color changes are
+ m_pTitleLabel->GetTextImage()->ClearColorChangeStream();
+
+ // We change the title's text color to match the colors of the matching model panel backgrounds
+ wchar_t *txt = wTemp;
+ int iWChars = 0;
+ Color colCustom;
+ while ( txt && *txt )
+ {
+ switch ( *txt )
+ {
+ case 0x01: // Normal color
+ m_pTitleLabel->GetTextImage()->AddColorChange( Color(235,226,202,255), iWChars );
+ break;
+ case 0x02: // Item 1 color
+ m_pTitleLabel->GetTextImage()->AddColorChange( Color(112,176,74,255), iWChars );
+ break;
+ case 0x03: // Item 2 color
+ m_pTitleLabel->GetTextImage()->AddColorChange( Color(71,98,145,255), iWChars );
+ break;
+ default:
+ break;
+ }
+ txt++;
+ iWChars++;
+ }
+ }
+ }
+
+ vgui::Panel *pToolIcon = FindChildByName( "tool_icon" );
+ if ( pToolIcon )
+ {
+ pToolIcon->SetMouseInputEnabled( false );
+ pToolIcon->SetKeyBoardInputEnabled( false );
+ }
+ vgui::Panel *pSubjectIcon = FindChildByName( "subject_icon" );
+ if ( pSubjectIcon )
+ {
+ pSubjectIcon->SetMouseInputEnabled( false );
+ pSubjectIcon->SetKeyBoardInputEnabled( false );
+ }
+
+ // @note Tom Bui: because the children have already applied their scheme settings and/or performed their layout before
+ // this dialog has had a chance to load its res file, we need to manually invalidate the layout of these children
+ // to make sure that the settings are valid with respect to what the parent wants
+ m_pToolModelPanel->UpdatePanels();
+ m_pSubjectModelPanel->UpdatePanels();
+ m_pMouseOverItemPanel->SetBorder( pScheme->GetBorder("LoadoutItemPopupBorder") );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseToolUsageDialog::PerformLayout()
+{
+ BaseClass::PerformLayout();
+ // if ( m_pMouseOverItemPanel->IsVisible() )
+ // {
+ // // The mouseover panel was visible. Fake a panel entry into the original panel to get it to show up again properly.
+ // if ( m_pItemPanelBeingMousedOver )
+ // {
+ // OnItemPanelEntered( m_pItemPanelBeingMousedOver );
+ // }
+ // else
+ // {
+ // HideMouseOverPanel();
+ // }
+ // }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseToolUsageDialog::OnCommand( const char *command )
+{
+ // in any case, our dialog is going away
+ TFModalStack()->PopModal( this );
+ SetVisible( false );
+ MarkForDeletion();
+
+#ifdef TF_CLIENT_DLL
+ const char *pSubjectBaseName = m_pSubjectModelPanel && m_pSubjectModelPanel->GetItem() && m_pSubjectModelPanel->GetItem()->GetItemDefinition()
+ ? m_pSubjectModelPanel->GetItem()->GetItemDefinition()->GetItemBaseName()
+ : "n/a";
+#endif
+
+ if ( !Q_stricmp( command, "apply" ) )
+ {
+#ifdef TF_CLIENT_DLL
+ C_CTFGameStats::ImmediateWriteInterfaceEvent( CFmtStr( "tool_usage_proceed(%s)", m_pszInternalPanelName ).Access(),
+ pSubjectBaseName );
+#endif
+ // Call before Apply() in case it creates new dialogs that wants to handle prevention
+ EconUI()->SetPreventClosure( false );
+ Apply();
+ }
+ else // "cancel"
+ {
+#ifdef TF_CLIENT_DLL
+ C_CTFGameStats::ImmediateWriteInterfaceEvent( CFmtStr( "tool_usage_cancel(%s)", m_pszInternalPanelName ).Access(),
+ pSubjectBaseName );
+#endif
+
+ EconUI()->SetPreventClosure( false );
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "inventory_updated" );
+ if ( event )
+ {
+ gameeventmanager->FireEventClientSide( event );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Utility function
+//-----------------------------------------------------------------------------
+void CBaseToolUsageDialog::OnKeyCodeTyped( vgui::KeyCode code )
+{
+ if( code == KEY_ESCAPE )
+ {
+ OnCommand( "cancel" );
+ }
+ else
+ {
+ BaseClass::OnKeyCodeTyped( code );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Utility function
+//-----------------------------------------------------------------------------
+void CBaseToolUsageDialog::OnKeyCodePressed( vgui::KeyCode code )
+{
+ ButtonCode_t nButtonCode = GetBaseButtonCode( code );
+
+ if (nButtonCode == KEY_XBUTTON_LEFT ||
+ nButtonCode == KEY_XSTICK1_LEFT ||
+ nButtonCode == KEY_XSTICK2_LEFT ||
+ nButtonCode == STEAMCONTROLLER_DPAD_LEFT ||
+ code == KEY_LEFT ||
+ nButtonCode == KEY_XBUTTON_RIGHT ||
+ nButtonCode == KEY_XSTICK1_RIGHT ||
+ nButtonCode == KEY_XSTICK2_RIGHT ||
+ nButtonCode == STEAMCONTROLLER_DPAD_RIGHT ||
+ code == KEY_RIGHT ||
+ nButtonCode == KEY_XBUTTON_UP ||
+ nButtonCode == KEY_XSTICK1_UP ||
+ nButtonCode == KEY_XSTICK2_UP ||
+ nButtonCode == STEAMCONTROLLER_DPAD_UP ||
+ code == KEY_UP ||
+ nButtonCode == KEY_XBUTTON_DOWN ||
+ nButtonCode == KEY_XSTICK1_DOWN ||
+ nButtonCode == KEY_XSTICK2_DOWN ||
+ nButtonCode == STEAMCONTROLLER_DPAD_DOWN ||
+ code == KEY_DOWN )
+ {
+ // eat all the movement keys so the selection doesn't update behind the dialog
+ }
+ else if( nButtonCode == KEY_XBUTTON_A || code == KEY_ENTER || nButtonCode == STEAMCONTROLLER_A )
+ {
+ OnCommand( "apply" );
+ }
+ else if( nButtonCode == KEY_XBUTTON_B || nButtonCode == STEAMCONTROLLER_B )
+ {
+ OnCommand( "cancel" );
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Utility function
+//-----------------------------------------------------------------------------
+void MakeModalAndBringToFront( vgui::EditablePanel *dialog )
+{
+ dialog->SetVisible( true );
+ if ( dialog->GetParent() == NULL )
+ {
+ dialog->MakePopup();
+ }
+ dialog->SetZPos( 10000 );
+ dialog->MoveToFront();
+ dialog->SetKeyBoardInputEnabled( true );
+ dialog->SetMouseInputEnabled( true );
+ TFModalStack()->PushModal( dialog );
+
+ EconUI()->SetPreventClosure( true );
+}
+
+//-----------------------------------------------------------------------------
+//
+// Given a tool and an item to apply the tool's effects upon,
+// gather required information from the user and
+// send a change request to the GC.
+//
+bool ApplyTool( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject )
+{
+ if ( !pTool || !pToolSubject )
+ return false;
+
+ if ( !CEconSharedToolSupport::ToolCanApplyTo( pTool, pToolSubject ) )
+ return false;
+
+ // this tool can be applied to this subject item
+ const IEconTool *pEconTool = pTool->GetStaticData()->GetEconTool();
+ if ( !pEconTool )
+ return false;
+
+ pEconTool->OnClientApplyTool( pTool, pToolSubject, pParent );
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: STRANGE COUNT TRANSFER
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+class CConfirmStrangeCountTransferApplicationDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmStrangeCountTransferApplicationDialog, CBaseToolUsageDialog );
+
+public:
+ CConfirmStrangeCountTransferApplicationDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject )
+ : CBaseToolUsageDialog( pParent, "ConfirmApplyStrangeCountTransferDialog", pTool, pToolSubject )
+ {
+ m_pItemSrc = NULL;
+ m_pItemDest = NULL;
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ LoadControlSettings( "Resource/UI/econ/ConfirmApplyDuckTokenDialog.res" ); // fix me
+
+ BaseClass::ApplySchemeSettings( pScheme );
+ }
+
+ virtual void Apply( void )
+ {
+ GCSDK::CProtoBufMsg<CMsgApplyStrangeCountTransfer> msg( k_EMsgGCApplyStrangeCountTransfer );
+
+ if ( !m_pItemSrc || !m_pItemDest )
+ return;
+
+ msg.Body().set_tool_item_id( m_pToolModelPanel->GetItem()->GetItemID() );
+ msg.Body().set_item_src_item_id( m_pItemSrc->GetItemID() );
+ msg.Body().set_item_dest_item_id( m_pItemDest->GetItemID() );
+ GCClientSystem()->BSendMessage( msg );
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "applied_strangecounttransfer", m_pToolModelPanel->GetItem()->GetItemDefIndex() );
+
+ GCClientSystem()->BSendMessage( msg );
+ }
+
+ bool SetItems( CEconItemView *pItemSrc, CEconItemView *pItemDest )
+ {
+ if ( !pItemSrc || !pItemDest )
+ return false;
+
+ if ( CEconTool_StrangeCountTransfer::AreItemsEligibleForStrangeCountTransfer( pItemSrc, pItemDest ) )
+ {
+ m_pItemSrc = pItemSrc;
+ m_pItemDest = pItemDest;
+
+ return true;
+ }
+
+ return false;
+ }
+
+private:
+ CEconItemView *m_pItemSrc;
+ CEconItemView *m_pItemDest;
+};
+
+/*static */bool CEconTool_StrangeCountTransfer::SetItems( CEconItemView *pItemSrc, CEconItemView *pItemDest )
+{
+ if ( !pItemSrc || !pItemDest )
+ return false;
+
+ if ( CEconTool_StrangeCountTransfer::AreItemsEligibleForStrangeCountTransfer( pItemSrc, pItemDest ) )
+ {
+ CEconTool_StrangeCountTransfer::m_pItemSrc = pItemSrc;
+ CEconTool_StrangeCountTransfer::m_pItemDest = pItemDest;
+
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_StrangeCountTransfer::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmStrangeCountTransferApplicationDialog *pDialog = vgui::SETUP_PANEL( new CConfirmStrangeCountTransferApplicationDialog( pParent, pTool, pSubject ) );
+ MakeModalAndBringToFront( pDialog );
+}
+
+
+// ****************************************************************************
+// STRANGE PARTS
+//-----------------------------------------------------------------------------
+// Purpose: Confirm / abort strange part application
+//-----------------------------------------------------------------------------
+class CConfirmStrangePartApplicationDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmStrangePartApplicationDialog, CBaseToolUsageDialog );
+
+public:
+ CConfirmStrangePartApplicationDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void Apply( void );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CConfirmStrangePartApplicationDialog::CConfirmStrangePartApplicationDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject )
+ : CBaseToolUsageDialog( pParent, "ConfirmApplyStrangePartApplicationDialog", pTool, pToolSubject )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmStrangePartApplicationDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ LoadControlSettings( "Resource/UI/econ/ConfirmApplyStrangePartApplicationDialog.res" );
+
+ int iRemainingStrangePartSlots = 0;
+ for ( int i = 0; i < GetKillEaterAttrCount(); i++ )
+ {
+ if ( !GetKillEaterAttr_IsUserCustomizable( i ) )
+ continue;
+
+ if ( !m_pSubjectModelPanel->GetItem()->FindAttribute( GetKillEaterAttr_Score( i ) ) )
+ ++iRemainingStrangePartSlots;
+ }
+
+ SetDialogVariable( "remaining_strange_part_slots", iRemainingStrangePartSlots );
+ SetDialogVariable( "maximum_strange_part_slots", GetKillEaterAttrCount_UserCustomizable() );
+ SetDialogVariable( "subject_item_def_name", GLocalizationProvider()->Find( m_pSubjectModelPanel->GetItem()->GetItemDefinition()->GetItemBaseName() ) );
+ SetDialogVariable( "slot_singular_plural", GLocalizationProvider()->Find( iRemainingStrangePartSlots == 1 ? "#Econ_FreeSlot_Singular" : "#Econ_FreeSlot_Plural" ) );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmStrangePartApplicationDialog::Apply()
+{
+ GCSDK::CProtoBufMsg<CMsgApplyStrangePart> msg( k_EMsgGCApplyStrangePart );
+
+ msg.Body().set_strange_part_item_id( m_pSubjectModelPanel->GetItem()->GetItemID() );
+ msg.Body().set_item_item_id( m_pToolModelPanel->GetItem()->GetItemID() );
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "applied_strange_part", m_pToolModelPanel->GetItem()->GetItemDefIndex() );
+
+ GCClientSystem()->BSendMessage( msg );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_StrangePart::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmStrangePartApplicationDialog *pDialog = vgui::SETUP_PANEL( new CConfirmStrangePartApplicationDialog( pParent, pTool, pSubject ) );
+ MakeModalAndBringToFront( pDialog );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Confirm / abort strange restriction application
+//-----------------------------------------------------------------------------
+class CConfirmStrangeRestrictionApplicationDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmStrangeRestrictionApplicationDialog, CBaseToolUsageDialog );
+
+public:
+ CConfirmStrangeRestrictionApplicationDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject, int iStrangeSlot, const char *pszStatLocalizationToken );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void Apply( void );
+
+private:
+ int m_iStrangeSlot;
+ const char *m_pszStatLocalizationToken;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CConfirmStrangeRestrictionApplicationDialog::CConfirmStrangeRestrictionApplicationDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject, int iStrangeSlot, const char *pszStatLocalizationToken )
+ : CBaseToolUsageDialog( pParent, "ConfirmApplyStrangeRestrictionApplicationDialog", pTool, pToolSubject )
+ , m_iStrangeSlot( iStrangeSlot )
+ , m_pszStatLocalizationToken( pszStatLocalizationToken )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmStrangeRestrictionApplicationDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ LoadControlSettings( "Resource/UI/econ/ConfirmApplyStrangeRestrictionApplicationDialog.res" );
+
+ SetDialogVariable( "stat_name", GLocalizationProvider()->Find( m_pszStatLocalizationToken ) );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmStrangeRestrictionApplicationDialog::Apply()
+{
+ GCSDK::CProtoBufMsg<CMsgApplyStrangeRestriction> msg( k_EMsgGCApplyStrangeRestriction );
+
+ msg.Body().set_strange_part_item_id( m_pSubjectModelPanel->GetItem()->GetItemID() );
+ msg.Body().set_item_item_id( m_pToolModelPanel->GetItem()->GetItemID() );
+ msg.Body().set_strange_attr_index( m_iStrangeSlot );
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "applied_strange_restriction", m_pToolModelPanel->GetItem()->GetItemDefIndex() );
+
+ GCClientSystem()->BSendMessage( msg );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CSelectStrangePartToRestrictDialog : public CComboBoxBackpackOverlayDialogBase
+{
+public:
+ DECLARE_CLASS_SIMPLE( CSelectStrangePartToRestrictDialog, CComboBoxBackpackOverlayDialogBase );
+
+public:
+ CSelectStrangePartToRestrictDialog( vgui::Panel *pParent, CEconItemView *pToolItem, CEconItemView *pSubjectItem )
+ : CComboBoxBackpackOverlayDialogBase( pParent, pSubjectItem )
+ , m_ToolItem( *pToolItem )
+ , m_SubjectItem( *pSubjectItem )
+ {
+ //
+ }
+
+private:
+ virtual void PopulateComboBoxOptions()
+ {
+ const wchar_t *pLocBase = GLocalizationProvider()->Find( "#ApplyStrangeRestrictionCombo" );
+
+ const CEconTool_StrangePartRestriction *pToolRestriction = m_ToolItem.GetItemDefinition()->GetTypedEconTool<CEconTool_StrangePartRestriction>();
+ Assert( pToolRestriction );
+
+ KeyValues *pKeyValues = new KeyValues( "data" );
+ for ( int i = 0; i < GetKillEaterAttrCount(); i++ )
+ {
+ uint32 unScoreType;
+ if ( !GetItemSchema()->BCanStrangeFilterApplyToStrangeSlotInItem( pToolRestriction->GetRestrictionType(), pToolRestriction->GetRestrictionValue(), &m_SubjectItem, i, &unScoreType ) )
+ continue;
+
+ const char *pszTypeLocKey = GetItemSchema()->GetKillEaterScoreTypeLocString( unScoreType );
+ if ( !pszTypeLocKey )
+ continue;
+
+ pKeyValues->SetInt( "data", i );
+ pKeyValues->SetString( "token", pszTypeLocKey );
+
+ GetComboBox()->AddItem( CConstructLocalizedString( pLocBase, GLocalizationProvider()->Find( pszTypeLocKey ) ), pKeyValues );
+ }
+ pKeyValues->deleteThis();
+
+ Assert( GetComboBox()->GetItemCount() > 0 );
+
+ GetComboBox()->ActivateItemByRow( 0 );
+ }
+
+ virtual void OnComboBoxApplication()
+ {
+ KeyValues *pKVActiveUserData = GetComboBox()->GetActiveItemUserData();
+ int iIndex = pKVActiveUserData ? pKVActiveUserData->GetInt( "data", -1 ) : -1;
+ if ( iIndex < 0 )
+ return;
+
+ // FIXME: CConfirmStrangePartApplicationDialog is wrong class
+ CConfirmStrangeRestrictionApplicationDialog *pDialog = vgui::SETUP_PANEL( new CConfirmStrangeRestrictionApplicationDialog( GetParent(), &m_ToolItem, &m_SubjectItem, iIndex, pKVActiveUserData ? pKVActiveUserData->GetString( "token", NULL ) : NULL ) );
+ MakeModalAndBringToFront( pDialog );
+ }
+
+ virtual const char *GetTitleLabelLocalizationToken() const { return "#ApplyStrangeRestrictionPartTitle"; }
+
+private:
+ CEconItemView m_ToolItem;
+ CEconItemView m_SubjectItem;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_StrangePartRestriction::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ if ( EconUI()->GetBackpackPanel() )
+ {
+ EconUI()->GetBackpackPanel()->SetComboBoxOverlaySelectionItem( pSubject );
+ }
+
+ CSelectStrangePartToRestrictDialog *pDialog = vgui::SETUP_PANEL( new CSelectStrangePartToRestrictDialog( pParent, pTool, pSubject ) );
+ MakeModalAndBringToFront( pDialog );
+}
+
+//-----------------------------------------------------------------------------
+class CWaitingDialog : public CGenericWaitingDialog
+{
+public:
+ CWaitingDialog( vgui::Panel *pParent ) : CGenericWaitingDialog( pParent )
+ {
+ }
+
+protected:
+ virtual void OnTimeout()
+ {
+ // Play an exciting sound!
+ vgui::surface()->PlaySound( "misc/achievement_earned.wav" );
+
+ // Show them the result item.
+ InventoryManager()->ShowItemsPickedUp( true );
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Confirm / abort card upgrade tool application
+//-----------------------------------------------------------------------------
+class CConfirmApplyStrangifierDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmApplyStrangifierDialog, CBaseToolUsageDialog );
+
+public:
+ CConfirmApplyStrangifierDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject, const char *pszPromptLocToken, const char *pszTransactionReason, const char *pszUpdatingText = "" )
+ : CBaseToolUsageDialog( pParent, "ConfirmApplyStrangifierDialog", pTool, pToolSubject )
+ , m_sPromptLocToken( pszPromptLocToken )
+ , m_sTransactionReason( pszTransactionReason )
+ , m_sUpdatingText( pszUpdatingText )
+ {
+ //
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ LoadControlSettings( "Resource/UI/econ/ConfirmApplyStrangifierDialog.res" );
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ CExLabel* pTextLabel = dynamic_cast<CExLabel*>( FindChildByName( "ConfirmLabel" ) );
+ if( pTextLabel && m_pToolModelPanel )
+ {
+ wchar_t *pszBaseString = g_pVGuiLocalize->Find( m_sPromptLocToken );
+ wchar_t wTempFinalString[1024] = { 0 };
+ if ( pszBaseString )
+ {
+ V_wcscpy_safe( wTempFinalString, pszBaseString );
+ }
+
+ // If the strangifier is untradable, add an extra warning in the prompt to let the user know
+ if( m_pToolModelPanel->GetItem() && !m_pToolModelPanel->GetItem()->IsTradable() &&
+ m_pSubjectModelPanel && m_pSubjectModelPanel->GetItem() )
+ {
+ wchar_t *pszUntradableString = g_pVGuiLocalize->Find( "ToolStrangifierUntradableWarning" );
+
+ // Stick the names of the items into the string
+ wchar_t wTempUntradable[1024] = { 0 };
+ g_pVGuiLocalize->ConstructString_safe( wTempUntradable, pszUntradableString, 2, m_pToolModelPanel->GetItem()->GetItemName(), m_pSubjectModelPanel->GetItem()->GetItemName() );
+
+ // Concat onto the the original string
+ V_wcscat_safe( wTempFinalString, wTempUntradable, sizeof( wTempUntradable ) );
+ }
+
+ pTextLabel->SetText( wTempFinalString );
+ }
+
+ }
+
+ virtual void Apply( void )
+ {
+ if ( m_pSubjectModelPanel->GetItem()->GetItemID() != INVALID_ITEM_ID )
+ {
+ GCSDK::CProtoBufMsg<CMsgApplyToolToItem> msg( k_EMsgGCApplyXifier );
+ msg.Body().set_tool_item_id( m_pToolModelPanel->GetItem()->GetItemID() );
+ msg.Body().set_subject_item_id( m_pSubjectModelPanel->GetItem()->GetItemID() );
+ GCClientSystem()->BSendMessage( msg );
+ }
+ else
+ {
+ GCSDK::CProtoBufMsg<CMsgApplyToolToBaseItem> msg( k_EMsgGCApplyBaseItemXifier );
+ msg.Body().set_tool_item_id( m_pToolModelPanel->GetItem()->GetItemID() );
+ msg.Body().set_baseitem_def_index( m_pSubjectModelPanel->GetItem()->GetStaticData()->GetDefinitionIndex() );
+ GCClientSystem()->BSendMessage( msg );
+ }
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), m_sTransactionReason.String() );
+
+ if ( *m_sUpdatingText.String() )
+ {
+ vgui::surface()->PlaySound( "ui/item_gift_wrap_use.wav" );
+ ShowWaitingDialog( new CWaitingDialog( NULL ), m_sUpdatingText.String(), true, false, 5.0f );
+ }
+ }
+
+private:
+ CUtlString m_sPromptLocToken;
+ CUtlString m_sTransactionReason;
+ CUtlString m_sUpdatingText;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_Strangifier::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmApplyStrangifierDialog *pDialog = vgui::SETUP_PANEL( new CConfirmApplyStrangifierDialog( pParent, pTool, pSubject, "ToolStrangifierConfirm", "strangified_item" ) );
+ MakeModalAndBringToFront( pDialog );
+}
+//-----------------------------------------------------------------------------
+void CEconTool_KillStreakifier::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmApplyStrangifierDialog *pDialog = vgui::SETUP_PANEL( new CConfirmApplyStrangifierDialog( pParent, pTool, pSubject, "ToolKillStreakifierConfirm", "killstreakified_item" ) );
+ MakeModalAndBringToFront( pDialog );
+}
+//-----------------------------------------------------------------------------
+void CEconTool_Festivizer::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmApplyStrangifierDialog *pDialog = vgui::SETUP_PANEL( new CConfirmApplyStrangifierDialog( pParent, pTool, pSubject, "ToolFestivizerConfirm", "festivized_item", "#ToolFestivizerInProgress" ) );
+ MakeModalAndBringToFront( pDialog );
+}
+//-----------------------------------------------------------------------------
+void CEconTool_Unusualifier::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmApplyStrangifierDialog *pDialog = vgui::SETUP_PANEL( new CConfirmApplyStrangifierDialog( pParent, pTool, pSubject, "ToolUnusualifierConfirm", "unusualified_item", "#ToolUnusualifierInProgress" ) );
+ MakeModalAndBringToFront( pDialog );
+}
+
+//-----------------------------------------------------------------------------
+class CConfirmUseItemEaterRechargerDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmUseItemEaterRechargerDialog, CBaseToolUsageDialog );
+
+public:
+ CConfirmUseItemEaterRechargerDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject )
+ : CBaseToolUsageDialog( pParent, "ConfirmUseItemEaterRechargerDialog", pTool, pToolSubject )
+ {
+
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ LoadControlSettings( "Resource/UI/econ/ConfirmUseItemEaterRechargerDialog.res" );
+
+ // Find the number of charges to add by looking at item tool rescritions.
+ const CEconTool_ItemEaterRecharger *pTool = m_pToolModelPanel->GetItem()->GetItemDefinition()->GetTypedEconTool<CEconTool_ItemEaterRecharger>();
+ if ( pTool )
+ {
+ int iCharges = pTool->GetChargesForItemDefId( m_pSubjectModelPanel->GetItem()->GetItemDefIndex() );
+ SetDialogVariable( "charges_added", iCharges );
+ }
+
+ BaseClass::ApplySchemeSettings( pScheme );
+ }
+
+ virtual void Apply( void )
+ {
+ GCSDK::CProtoBufMsg<CMsgApplyToolToItem> msg( k_EMsgGCItemEaterRecharger );
+ msg.Body().set_tool_item_id( m_pToolModelPanel->GetItem()->GetItemID() );
+ msg.Body().set_subject_item_id( m_pSubjectModelPanel->GetItem()->GetItemID() );
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "recharging_item" );
+
+ GCClientSystem()->BSendMessage( msg );
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_ItemEaterRecharger::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmUseItemEaterRechargerDialog *pDialog = vgui::SETUP_PANEL( new CConfirmUseItemEaterRechargerDialog( pParent, pTool, pSubject ) );
+ MakeModalAndBringToFront( pDialog );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Confirm / abort card upgrade tool application
+//-----------------------------------------------------------------------------
+class CConfirmCardUpgradeApplicationDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmCardUpgradeApplicationDialog, CBaseToolUsageDialog );
+
+public:
+ CConfirmCardUpgradeApplicationDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject )
+ : CBaseToolUsageDialog( pParent, "ConfirmApplyCardUpgradeApplicationDialog", pTool, pToolSubject )
+ {
+ //
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ LoadControlSettings( "Resource/UI/econ/ConfirmApplyCardUpgradeApplicationDialog.res" );
+
+ // See how many card upgrades have already been applied
+ CCountUserGeneratedAttributeIterator countIterator;
+ m_pSubjectModelPanel->GetItem()->IterateAttributes( &countIterator );
+
+ int iRemainingStrangePartSlots = GetMaxCardUpgradesPerItem() - countIterator.GetCount();
+
+ SetDialogVariable( "remaining_upgrade_card_slots", iRemainingStrangePartSlots );
+ SetDialogVariable( "subject_item_def_name", GLocalizationProvider()->Find( m_pSubjectModelPanel->GetItem()->GetItemDefinition()->GetItemBaseName() ) );
+ SetDialogVariable( "slot_singular_plural", GLocalizationProvider()->Find( iRemainingStrangePartSlots == 1 ? "#Econ_FreeSlot_Singular" : "#Econ_FreeSlot_Plural" ) );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+ }
+
+ virtual void Apply( void )
+ {
+ GCSDK::CProtoBufMsg<CMsgApplyUpgradeCard> msg( k_EMsgGCApplyUpgradeCard );
+
+ msg.Body().set_upgrade_card_item_id( m_pSubjectModelPanel->GetItem()->GetItemID() );
+ msg.Body().set_subject_item_id( m_pToolModelPanel->GetItem()->GetItemID() );
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "applied_upgrade_card", m_pToolModelPanel->GetItem()->GetItemDefIndex() );
+
+ GCClientSystem()->BSendMessage( msg );
+ }
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_UpgradeCard::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmCardUpgradeApplicationDialog *pDialog = vgui::SETUP_PANEL( new CConfirmCardUpgradeApplicationDialog( pParent, pTool, pSubject ) );
+ MakeModalAndBringToFront( pDialog );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CConfirmTransmogrifyApplicationDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmTransmogrifyApplicationDialog, CBaseToolUsageDialog );
+
+public:
+ CConfirmTransmogrifyApplicationDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject )
+ : CBaseToolUsageDialog( pParent, "ConfirmTransmogrifyApplicationDialog", pTool, pToolSubject )
+ {
+ //
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ LoadControlSettings( "Resource/UI/econ/ConfirmTransmogrifyApplicationDialog.res" );
+
+ const CEconTool_ClassTransmogrifier *pTool = m_pToolModelPanel->GetItem()->GetItemDefinition()->GetTypedEconTool<CEconTool_ClassTransmogrifier>();
+ Assert( pTool );
+
+ if ( pTool )
+ {
+ int iOutputClass = pTool->GetOutputClass();
+ if ( iOutputClass > 0 && iOutputClass < LOADOUT_COUNT )
+ {
+ SetDialogVariable( "output_class", GLocalizationProvider()->Find( g_aPlayerClassNames[ iOutputClass ] ) );
+ }
+ }
+
+ BaseClass::ApplySchemeSettings( pScheme );
+ }
+
+ virtual void Apply( void )
+ {
+ GCSDK::CProtoBufMsg<CMsgApplyToolToItem> msg( k_EMsgGCApplyClassTransmogrifier );
+
+ msg.Body().set_tool_item_id( m_pToolModelPanel->GetItem()->GetItemID() );
+ msg.Body().set_subject_item_id( m_pSubjectModelPanel->GetItem()->GetItemID() );
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "applied_transmogrifier", m_pToolModelPanel->GetItem()->GetItemDefIndex() );
+
+ GCClientSystem()->BSendMessage( msg );
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_ClassTransmogrifier::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmTransmogrifyApplicationDialog *pDialog = vgui::SETUP_PANEL( new CConfirmTransmogrifyApplicationDialog( pParent, pTool, pSubject ) );
+ MakeModalAndBringToFront( pDialog );
+}
+
+#ifdef TF_CLIENT_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CConfirmSpellbookPageApplicationDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmSpellbookPageApplicationDialog, CBaseToolUsageDialog );
+
+public:
+ CConfirmSpellbookPageApplicationDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject )
+ : CBaseToolUsageDialog( pParent, "ConfirmSpellbookPageApplicationDialog", pTool, pToolSubject )
+ {
+ //
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ LoadControlSettings( "Resource/UI/econ/ConfirmSpellbookPageApplicationDialog.res" );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+ }
+
+ virtual void Apply( void )
+ {
+ GCSDK::CProtoBufMsg<CMsgApplyToolToItem> msg( k_EMsgGCApplyHalloweenSpellbookPage );
+
+ msg.Body().set_tool_item_id( m_pToolModelPanel->GetItem()->GetItemID() );
+ msg.Body().set_subject_item_id( m_pSubjectModelPanel->GetItem()->GetItemID() );
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "applied_spellbook_page", m_pToolModelPanel->GetItem()->GetItemDefIndex() );
+
+ GCClientSystem()->BSendMessage( msg );
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_TFSpellbookPage::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmSpellbookPageApplicationDialog *pDialog = vgui::SETUP_PANEL( new CConfirmSpellbookPageApplicationDialog( pParent, pTool, pSubject ) );
+ MakeModalAndBringToFront( pDialog );
+}
+
+//-----------------------------------------------------------------------------
+class CConfirmDuckTokenApplicationDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmDuckTokenApplicationDialog, CBaseToolUsageDialog );
+
+public:
+ CConfirmDuckTokenApplicationDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject )
+ : CBaseToolUsageDialog( pParent, "ConfirmApplyDuckTokenDialog", pTool, pToolSubject )
+ {
+ //
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ LoadControlSettings( "Resource/UI/econ/ConfirmApplyDuckTokenDialog.res" );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+ }
+
+ virtual void Apply( void )
+ {
+ GCSDK::CProtoBufMsg<CMsgApplyToolToItem> msg( k_EMsgGCApplyDuckToken );
+
+ msg.Body().set_tool_item_id( m_pToolModelPanel->GetItem()->GetItemID() );
+ msg.Body().set_subject_item_id( m_pSubjectModelPanel->GetItem()->GetItemID() );
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "applied_ducktoken", m_pToolModelPanel->GetItem()->GetItemDefIndex() );
+
+ GCClientSystem()->BSendMessage( msg );
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_DuckToken::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmDuckTokenApplicationDialog *pDialog = vgui::SETUP_PANEL( new CConfirmDuckTokenApplicationDialog( pParent, pTool, pSubject ) );
+ MakeModalAndBringToFront( pDialog );
+}
+#endif // TF_CLIENT_DLL \ No newline at end of file
diff --git a/game/client/econ/tool_items/tool_items.h b/game/client/econ/tool_items/tool_items.h
new file mode 100644
index 0000000..14bfb36
--- /dev/null
+++ b/game/client/econ/tool_items/tool_items.h
@@ -0,0 +1,58 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TOOL_ITEMS_H
+#define TOOL_ITEMS_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "item_model_panel.h"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CBaseToolUsageDialog : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CBaseToolUsageDialog, vgui::EditablePanel );
+
+public:
+ CBaseToolUsageDialog( vgui::Panel *pParent, const char *panelName, CEconItemView *pTool, CEconItemView *pToolSubject );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *scheme );
+ virtual void PerformLayout();
+ virtual void OnCommand( const char *command );
+ virtual void OnKeyCodeTyped( vgui::KeyCode code ) OVERRIDE;
+ virtual void OnKeyCodePressed( vgui::KeyCode code ) OVERRIDE;
+
+ virtual void Apply( void ) { return; }
+
+ inline CEconItemView *GetToolItem() { return m_pToolModelPanel->GetItem(); };
+ inline CEconItemView *GetSubjectItem() { return m_pSubjectModelPanel->GetItem(); };
+
+protected:
+ CItemModelPanel *m_pToolModelPanel;
+ CItemModelPanel *m_pSubjectModelPanel;
+ CItemModelPanel *m_pMouseOverItemPanel;
+ CItemModelPanelToolTip *m_pMouseOverTooltip;
+ vgui::Label *m_pTitleLabel;
+
+ const char *m_pszInternalPanelName;
+};
+
+
+// Utility function for tool dialogs.
+void MakeModalAndBringToFront( vgui::EditablePanel *dialog );
+
+bool ToolCanApplyTo( CEconItemView *pTool, CEconItemView *pToolSubject );
+
+// Given a tool and an item to apply the tool's effects upon,
+// gather required information from the user and
+// send a change request to the GC.
+bool ApplyTool( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject );
+
+#endif // TOOL_ITEMS_H