diff options
Diffstat (limited to 'game/client/econ/tool_items')
| -rw-r--r-- | game/client/econ/tool_items/custom_texture_cache.cpp | 1111 | ||||
| -rw-r--r-- | game/client/econ/tool_items/custom_texture_cache.h | 83 | ||||
| -rw-r--r-- | game/client/econ/tool_items/custom_texture_tool.cpp | 2766 | ||||
| -rw-r--r-- | game/client/econ/tool_items/decoder_ring_tool.cpp | 213 | ||||
| -rw-r--r-- | game/client/econ/tool_items/decoder_ring_tool.h | 14 | ||||
| -rw-r--r-- | game/client/econ/tool_items/gift_wrap_tool.cpp | 241 | ||||
| -rw-r--r-- | game/client/econ/tool_items/gift_wrap_tool.h | 14 | ||||
| -rw-r--r-- | game/client/econ/tool_items/paint_can_tool.cpp | 205 | ||||
| -rw-r--r-- | game/client/econ/tool_items/paint_can_tool.h | 15 | ||||
| -rw-r--r-- | game/client/econ/tool_items/rename_tool_ui.cpp | 306 | ||||
| -rw-r--r-- | game/client/econ/tool_items/rename_tool_ui.h | 74 | ||||
| -rw-r--r-- | game/client/econ/tool_items/tool_items.cpp | 968 | ||||
| -rw-r--r-- | game/client/econ/tool_items/tool_items.h | 58 |
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 |