while( *pszInput != '\0' && *pszInput != ':' )
        pszInput++;

    if( *pszInput == '\0' )
        return nullptr;

    pszInput++;

    // Is this a pointer?
    if( *pszInput == 'p' || *pszInput == '*' )
        chPointer = *(pszInput++);

    // Get the general type.
    if( *pszInput == '\0' )
        return nullptr;

    chItemType = *(pszInput++);

    if( strchr("124cCesStlLfdmMbox", chItemType) == nullptr )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Unrecognized item type: %c", chItemType);
        return nullptr;
    }

    // If this is an object, we extract the type of the object.
    int i = 0;  // TODO: Describe why i needs to span chItemType blocks.

    if( chItemType == 'o' )
    {
        for( i = 0; pszInput[i] != '\0' && pszInput[i] != ','; i++ ) {}
        if( pszInput[i] == '\0' )
            return nullptr;

        pszItemObjectType = static_cast<char *>(CPLMalloc(i + 1));
        strncpy(pszItemObjectType, pszInput, i);
        pszItemObjectType[i] = '\0';

        pszInput += i + 1;
    }

    // If this is an inline object, we need to skip past the
    // definition, and then extract the object class name.
    // We ignore the actual definition, so if the object type isn't
    // already defined, things will not work properly.  See the
    // file lceugr250_00_pct.aux for an example of inline defs.
    if( chItemType == 'x' && *pszInput == '{' )
    {
        int nBraceDepth = 1;
        pszInput++;

        // Skip past the definition.
        while( nBraceDepth > 0 && *pszInput != '\0' )
        {
            if( *pszInput == '{' )
                nBraceDepth++;
            else if( *pszInput == '}' )
                nBraceDepth--;

            pszInput++;
        }
        if( *pszInput == '\0' )
            return nullptr;

        chItemType = 'o';

        // Find the comma terminating the type name.
        for( i = 0; pszInput[i] != '\0' && pszInput[i] != ','; i++ ) {}
        if( pszInput[i] == '\0' )
            return nullptr;

        pszItemObjectType = static_cast<char *>(CPLMalloc(i + 1));
        strncpy(pszItemObjectType, pszInput, i);
        pszItemObjectType[i] = '\0';

        pszInput += i + 1;
    }

    // If this is an enumeration we have to extract all the
    if( chItemType == 'e' )
    {
        const int nEnumCount = atoi(pszInput);

        if( nEnumCount < 0 || nEnumCount > 100000 )
            return nullptr;

        pszInput = strchr(pszInput, ':');
        if( pszInput == nullptr )
            return nullptr;

        pszInput++;

        papszEnumNames =
            static_cast<char **>(VSICalloc(sizeof(char *), nEnumCount + 1));
        if( papszEnumNames == nullptr )
            return nullptr;

        for( int iEnum = 0; iEnum < nEnumCount; iEnum++ )
        {
            for( i = 0; pszInput[i] != '\0' && pszInput[i] != ','; i++ ) {}

            if( pszInput[i] != ',' )
                return nullptr;

            char *pszToken = static_cast<char *>(CPLMalloc(i + 1));
            strncpy(pszToken, pszInput, i);
            pszToken[i] = '\0';

            papszEnumNames[iEnum] = pszToken;

            pszInput += i + 1;
        }
    }

    // Extract the field name.
    for( i = 0; pszInput[i] != '\0' && pszInput[i] != ','; i++ ) {}
    if( pszInput[i] == '\0' )
        return nullptr;

    pszFieldName = static_cast<char *>(CPLMalloc(i + 1));
    strncpy(pszFieldName, pszInput, i);
    pszFieldName[i] = '\0';

    pszInput += i + 1;

    return pszInput;
}

/************************************************************************/
/*                            CompleteDefn()                            */
/*                                                                      */
/*      Establish size, and pointers to component types.                */
/************************************************************************/

bool HFAField::CompleteDefn( HFADictionary *poDict )

{
    // Get a reference to the type object if we have a type name
    // for this field (not a built in).
    if( pszItemObjectType != nullptr )
        poItemObjectType = poDict->FindType(pszItemObjectType);

    // Figure out the size.
    if( chPointer == 'p' )
    {
        nBytes = -1;  // We can't know the instance size.
    }
    else if( poItemObjectType != nullptr )
    {
        if( !poItemObjectType->CompleteDefn(poDict) )
            return false;
        if( poItemObjectType->nBytes == -1 )
            nBytes = -1;
        else if( poItemObjectType->nBytes != 0 &&
                 nItemCount > INT_MAX / poItemObjectType->nBytes )
            nBytes = -1;
        else
            nBytes = poItemObjectType->nBytes * nItemCount;

        if( chPointer == '*' && nBytes != -1 )
        {
            if( nBytes > INT_MAX - 8 )
                nBytes = -1;
            else
                nBytes += 8;  // Count, and offset.
        }
    }
    else
    {
        const int nItemSize = poDict->GetItemSize(chItemType);
        if( nItemSize != 0 && nItemCount > INT_MAX / nItemSize )
            nBytes = -1;
        else
            nBytes = nItemSize * nItemCount;
    }
    return true;
}

/************************************************************************/
/*                                Dump()                                */
/************************************************************************/

void HFAField::Dump( FILE *fp )

{
    const char *pszTypeName;

    switch( chItemType )
    {
    case '1':
        pszTypeName = "U1";
        break;

    case '2':
        pszTypeName = "U2";
        break;

    case '4':
        pszTypeName = "U4";
        break;

    case 'c':
        pszTypeName = "UCHAR";
        break;

    case 'C':
        pszTypeName = "CHAR";
        break;

    case 'e':
        pszTypeName = "ENUM";
        break;

    case 's':
        pszTypeName = "USHORT";
        break;

    case 'S':
        pszTypeName = "SHORT";
        break;

    case 't':
        pszTypeName = "TIME";
        break;

    case 'l':
        pszTypeName = "ULONG";
        break;

    case 'L':
        pszTypeName = "LONG";
        break;

    case 'f':
        pszTypeName = "FLOAT";
        break;

    case 'd':
        pszTypeName = "DOUBLE";
        break;

    case 'm':
        pszTypeName = "COMPLEX";
        break;

    case 'M':
        pszTypeName = "DCOMPLEX";
        break;

    case 'b':
        pszTypeName = "BASEDATA";
        break;

    case 'o':
        pszTypeName = pszItemObjectType;
        break;

    case 'x':
        pszTypeName = "InlineType";
        break;

    default:
        CPLAssert(false);
        pszTypeName = "Unknown";
    }

    CPL_IGNORE_RET_VAL(
        VSIFPrintf(fp, "    %-19s %c %s[%d];\n",
                   pszTypeName,
                   chPointer ? chPointer : ' ',
                   pszFieldName, nItemCount));

    if( papszEnumNames != nullptr )
    {
        for( int i = 0; papszEnumNames[i] != nullptr; i++ )
        {
            CPL_IGNORE_RET_VAL(
                VSIFPrintf(fp, "        %s=%d\n", papszEnumNames[i], i));
        }
    }
}

/************************************************************************/
/*                            SetInstValue()                            */
/************************************************************************/

CPLErr
HFAField::SetInstValue( const char *pszField, int nIndexValue,
                        GByte *pabyData, GUInt32 nDataOffset, int nDataSize,
                        char chReqType, void *pValue )

{
    // If this field contains a pointer, then we will adjust the
    // data offset relative to it.
    if( chPointer != '\0' )
    {
        GUInt32 nCount = 0;

        // The count returned for BASEDATA's are the contents,
        // but here we really want to mark it as one BASEDATA instance
        if( chItemType == 'b' )
        {
            nCount = 1;
        }
        // Set the size from string length.
        else if( chReqType == 's' && (chItemType == 'c' || chItemType == 'C'))
        {
            if( pValue != nullptr )
                nCount = static_cast<GUInt32>(strlen((char *)pValue) + 1);
        }
        // Set size based on index. Assumes in-order setting of array.
        else
        {
            nCount = nIndexValue + 1;
        }

        if( static_cast<int>(nCount) + 8 > nDataSize )
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Attempt to extend field %s in node past end of data, "
                     "not currently supported.",
                     pszField);
            return CE_Failure;
        }

        // We will update the object count iff we are writing beyond the end.
        GUInt32 nOffset = 0;
        memcpy(&nOffset, pabyData, 4);
        HFAStandard(4, &nOffset);
        if( nOffset < nCount )
        {
            nOffset = nCount;
            HFAStandard(4, &nOffset);
            memcpy(pabyData, &nOffset, 4);
        }

        if( pValue == nullptr )
            nOffset = 0;
        else
            nOffset = nDataOffset + 8;
        HFAStandard(4, &nOffset);
        memcpy(pabyData + 4, &nOffset, 4);

        pabyData += 8;

        nDataOffset += 8;
        nDataSize -= 8;
    }

    // Pointers to char or uchar arrays requested as strings are
    // handled as a special case.
    if( (chItemType == 'c' || chItemType == 'C') && chReqType == 's' )
    {
        int nBytesToCopy = 0;

        if( nBytes == -1 )
        {
            if( pValue != nullptr )
                nBytesToCopy = static_cast<int>(strlen((char *)pValue) + 1);
        }
        else
        {
            nBytesToCopy = nBytes;
        }

        if( nBytesToCopy > nDataSize )
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Attempt to extend field %s in node past end of data "
                     "not currently supported.",
                     pszField);
            return CE_Failure;
        }

        memset(pabyData, 0, nBytesToCopy);

        if( pValue != nullptr )
            strncpy((char *)pabyData, (char *)pValue, nBytesToCopy);

        return CE_None;
    }

    // Translate the passed type into different representations.
    int nIntValue = 0;
    double dfDoubleValue = 0.0;

    if( chReqType == 's' )
    {
        CPLAssert(pValue != nullptr);
        nIntValue = atoi((char *)pValue);
        dfDoubleValue = CPLAtof((char *)pValue);
    }
    else if( chReqType == 'd' )
    {
        CPLAssert(pValue != nullptr);
        dfDoubleValue = *((double *)pValue);
        if( dfDoubleValue > INT_MAX )
            nIntValue = INT_MAX;
        else if( dfDoubleValue < INT_MIN )
            nIntValue = INT_MIN;
        else
            nIntValue = static_cast<int>(dfDoubleValue);
    }
    else if( chReqType == 'i' )
    {
        CPLAssert(pValue != nullptr);
        nIntValue = *((int *)pValue);
        dfDoubleValue = nIntValue;
    }
    else if( chReqType == 'p' )
    {
        CPLError(
            CE_Failure, CPLE_NotSupported,
            "HFAField::SetInstValue() not supported yet for pointer values.");

        return CE_Failure;
    }
    else
    {
        CPLAssert(false);
        return CE_Failure;
    }

    // Handle by type.
    switch( chItemType )
    {
      case 'c':
      case 'C':
        if( nIndexValue + 1 > nDataSize )
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Attempt to extend field %s in node past end of data, "
                     "not currently supported.",
                     pszField);
              return CE_Failure;
        }

        if( chReqType == 's' )
        {
            CPLAssert(pValue != nullptr);
            pabyData[nIndexValue] = ((char *)pValue)[0];
        }
        else
        {
            pabyData[nIndexValue] = static_cast<char>(nIntValue);
        }
        break;

      case 'e':
      case 's':
      {
          if( chItemType == 'e' && chReqType == 's' )
          {
              CPLAssert(pValue != nullptr);
              nIntValue = CSLFindString(papszEnumNames, (char *) pValue);
              if( nIntValue == -1 )
              {
                  CPLError(CE_Failure, CPLE_AppDefined,
                            "Attempt to set enumerated field with unknown"
                            " value `%s'.",
                            (char *)pValue);
                  return CE_Failure;
              }
          }

          if( nIndexValue*2 + 2 > nDataSize )
          {
              CPLError(CE_Failure, CPLE_AppDefined,
                       "Attempt to extend field %s in node past end of data, "
                       "not currently supported.",
                       pszField);
              return CE_Failure;
          }

          // TODO(schwehr): Warn on clamping.
          unsigned short nNumber = static_cast<unsigned short>(nIntValue);
          HFAStandard(2, &nNumber);
          memcpy(pabyData + nIndexValue * 2, &nNumber, 2);
      }
      break;

      case 'S':
      {
          if( nIndexValue * 2 + 2 > nDataSize )
          {
              CPLError(CE_Failure, CPLE_AppDefined,
                       "Attempt to extend field %s in node past end of data, "
                       "not currently supported.",
                       pszField);
              return CE_Failure;
          }

          // TODO(schwehr): Warn on clamping.
          short nNumber = static_cast<short>(nIntValue);
          HFAStandard(2, &nNumber);
          memcpy(pabyData + nIndexValue * 2, &nNumber, 2);
      }
      break;

      case 't':
      case 'l':
      {
          if( nIndexValue * 4 + 4 > nDataSize )
          {
              CPLError(CE_Failure, CPLE_AppDefined,
                       "Attempt to extend field %s in node past end of data, "
                       "not currently supported.",
                       pszField);
              return CE_Failure;
          }

          GUInt32 nNumber = nIntValue;
          HFAStandard(4, &nNumber);
          memcpy(pabyData + nIndexValue * 4, &nNumber, 4);
      }
      break;

      case 'L':
      {
          if( nIndexValue * 4 + 4 > nDataSize )
          {
              CPLError(CE_Failure, CPLE_AppDefined,
                       "Attempt to extend field %s in node past end of data, "
                       "not currently supported.",
                       pszField);
              return CE_Failure;
          }

          GInt32 nNumber = nIntValue;
          HFAStandard(4, &nNumber);
          memcpy(pabyData + nIndexValue * 4, &nNumber, 4);
      }
      break;

      case 'f':
      {
          if( nIndexValue * 4 + 4 > nDataSize )
          {
              CPLError(CE_Failure, CPLE_AppDefined,
                       "Attempt to extend field %s in node past end of data, "
                       "not currently supported.",
                       pszField);
              return CE_Failure;
          }

          // TODO(schwehr): Warn on clamping.
          float fNumber = static_cast<float>(dfDoubleValue);
          HFAStandard(4, &fNumber);
          memcpy(pabyData + nIndexValue * 4, &fNumber, 4);
      }
      break;

      case 'd':
      {
          if( nIndexValue * 8 + 8 > nDataSize )
          {
              CPLError(CE_Failure, CPLE_AppDefined,
                       "Attempt to extend field %s in node past end of data, "
                       "not currently supported.",
                       pszField);
              return CE_Failure;
          }

          double dfNumber = dfDoubleValue;
          HFAStandard(8, &dfNumber);
          memcpy(pabyData + nIndexValue * 8, &dfNumber, 8);
      }
      break;

    case 'b':
    {
        // Extract existing rows, columns, and datatype.
        GInt32 nRows = 1;  // TODO(schwehr): Why init to 1 instead of 0?
        memcpy(&nRows, pabyData, 4);
        HFAStandard(4, &nRows);

        GInt32 nColumns = 1;  // TODO(schwehr): Why init to 1 instead of 0?
        memcpy(&nColumns, pabyData + 4, 4);
        HFAStandard(4, &nColumns);

        GInt16 nBaseItemType = 0;
        memcpy(&nBaseItemType, pabyData + 8, 2);
        HFAStandard(2, &nBaseItemType);

        // Are we using special index values to update the rows, columns
        // or type?

        if( nIndexValue == -3 )
            nBaseItemType = static_cast<GInt16>(nIntValue);
        else if( nIndexValue == -2 )
            nColumns = nIntValue;
        else if( nIndexValue == -1 )
            nRows = nIntValue;

        if( nIndexValue < -3 || nIndexValue >= nRows * nColumns )
            return CE_Failure;

        // Write back the rows, columns and basedatatype.
        HFAStandard(4, &nRows);
        memcpy(pabyData, &nRows, 4);
        HFAStandard(4, &nColumns);
        memcpy(pabyData + 4, &nColumns, 4);
        HFAStandard(2, &nBaseItemType);
        memcpy(pabyData + 8, &nBaseItemType, 2);
        HFAStandard(2, &nBaseItemType);  // Swap back for our use.

        if( nBaseItemType < EPT_MIN || nBaseItemType > EPT_MAX )
            return CE_Failure;
        const EPTType eBaseItemType = static_cast<EPTType>(nBaseItemType);

        // We ignore the 2 byte objecttype value.

        nDataSize -= 12;

        if( nIndexValue >= 0 )
        {
            if( (nIndexValue + 1) * (HFAGetDataTypeBits(eBaseItemType) / 8)
                > nDataSize )
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Attempt to extend field %s in node past end of "
                         "data, not currently supported.",
                         pszField);
                return CE_Failure;
            }

            if( eBaseItemType == EPT_f64 )
            {
                double dfNumber = dfDoubleValue;

                HFAStandard(8, &dfNumber);
                memcpy(pabyData + 12 + nIndexValue * 8, &dfNumber, 8);
            }
            else if( eBaseItemType == EPT_u8 )
            {
                // TODO(schwehr): Warn on clamping.
                unsigned char nNumber =
                    static_cast<unsigned char>(dfDoubleValue);
                memcpy(pabyData + 12 + nIndexValue, &nNumber, 1);
            }
            else
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Setting basedata field %s with type %s "
                         "not currently supported.",
                         pszField, HFAGetDataTypeName(eBaseItemType));
                return CE_Failure;
            }
        }
    }
    break;

      case 'o':
        if( poItemObjectType != nullptr )
        {
            int nExtraOffset = 0;

            if( poItemObjectType->nBytes > 0 )
            {
                if( nIndexValue != 0 &&
                    poItemObjectType->nBytes > INT_MAX / nIndexValue )
                {
                    return CE_Failure;
                }
                nExtraOffset = poItemObjectType->nBytes * nIndexValue;
            }
            else
            {
                for( int iIndexCounter = 0;
                     iIndexCounter < nIndexValue && nExtraOffset < nDataSize;
                     iIndexCounter++ )
                {
                    std::set<HFAField*> oVisitedFields;
                    const int nInc =
                        poItemObjectType->
                            GetInstBytes(pabyData + nExtraOffset,
                                         nDataSize - nExtraOffset,
                                         oVisitedFields);
                    if( nInc <= 0 || nExtraOffset > INT_MAX - nInc )
                    {
                        CPLError(CE_Failure, CPLE_AppDefined,
                                 "Invalid return value");
                        return CE_Failure;
                    }

                    nExtraOffset += nInc;
                }
            }

            if( nExtraOffset >= nDataSize )
                return CE_Failure;

            if( pszField != nullptr && strlen(pszField) > 0 )
            {
                return poItemObjectType->
                    SetInstValue(pszField, pabyData + nExtraOffset,
                                 nDataOffset + nExtraOffset,
                                 nDataSize - nExtraOffset,
                                 chReqType, pValue);
            }
            else
            {
                CPLAssert(false);
                return CE_Failure;
            }
        }
        break;

      default:
        CPLAssert(false);
        return CE_Failure;
        break;
    }

    return CE_None;
}

/************************************************************************/
/*                          ExtractInstValue()                          */
/*                                                                      */
/*      Extract the value of an instance of a field.                    */
/*                                                                      */
/*      pszField should be NULL if this field is not a                  */
/*      substructure.                                                   */
/************************************************************************/

bool
HFAField::ExtractInstValue( const char *pszField, int nIndexValue,
                            GByte *pabyData, GUInt32 nDataOffset, int nDataSize,
                            char chReqType, void *pReqReturn,
                            int *pnRemainingDataSize )

{
    const int nInstItemCount = GetInstCount(pabyData, nDataSize);

    if( pnRemainingDataSize )
        *pnRemainingDataSize = -1;

    // Check the index value is valid.
    // Eventually this will have to account for variable fields.
    if( nIndexValue < 0 || nIndexValue >= nInstItemCount )
    {
        if( chItemType == 'b' && nIndexValue >= -3 && nIndexValue < 0 )
            /* ok - special index values */;
        else
            return false;
    }

    // If this field contains a pointer, then we will adjust the
    // data offset relative to it.
    if( chPointer != '\0' )
    {
        if( nDataSize < 8 )
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
            return false;
        }

        GUInt32 nOffset = 0;
        memcpy(&nOffset, pabyData + 4, 4);
        HFAStandard(4, &nOffset);

#if DEBUG_VERBOSE
        if( nOffset != static_cast<GUInt32>(nDataOffset + 8) )
        {
            // TODO(schwehr): Debug why this is happening.
            CPLError(CE_Warning, CPLE_AppDefined,
                     "ExtractInstValue: "
                     "%s.%s points at %d, not %d as expected",
                     pszFieldName, pszField ? pszField : "",
                     nOffset, nDataOffset + 8);
        }
#endif

        pabyData += 8;
        nDataOffset += 8;
        nDataSize -= 8;
    }

    // Pointers to char or uchar arrays requested as strings are
    // handled as a special case.
    if( (chItemType == 'c' || chItemType == 'C') && chReqType == 's' )
    {
        *((GByte **)pReqReturn) = pabyData;
        if( pnRemainingDataSize )
            *pnRemainingDataSize = nDataSize;
        return pabyData != nullptr;
    }

    // Handle by type.
    char *pszStringRet = nullptr;
    int nIntRet = 0;
    double dfDoubleRet = 0.0;
    GByte *pabyRawData = nullptr;

    switch( chItemType )
    {
      case 'c':
      case 'C':
        if( nIndexValue >= nDataSize )
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
            return false;
        }
        nIntRet = pabyData[nIndexValue];
        dfDoubleRet = nIntRet;
        break;

      case 'e':
      case 's':
      {
          if( nIndexValue * 2 + 2 > nDataSize )
          {
              CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
              return false;
          }
          unsigned short nNumber = 0;
          memcpy(&nNumber, pabyData + nIndexValue * 2, 2);
          HFAStandard(2, &nNumber);
          nIntRet = nNumber;
          dfDoubleRet = nIntRet;

          if( chItemType == 'e'
              && nNumber < static_cast<unsigned>(CSLCount(papszEnumNames)) )
          {
              pszStringRet = papszEnumNames[nNumber];
          }
      }
      break;

      case 'S':
      {
          if( nIndexValue * 2 + 2 > nDataSize )
          {
              CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
              return false;
          }
          short nNumber = 0;
          memcpy(&nNumber, pabyData + nIndexValue * 2, 2);
          HFAStandard(2, &nNumber);
          nIntRet = nNumber;
          dfDoubleRet = nIntRet;
      }
      break;

      case 't':
      case 'l':
      {
          if( nIndexValue * 4 + 4 > nDataSize )
          {
              CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
              return false;
          }
          GUInt32 nNumber = 0;
          memcpy(&nNumber, pabyData + nIndexValue * 4, 4);
          HFAStandard(4, &nNumber);
          nIntRet = nNumber;
          dfDoubleRet = nIntRet;
      }
      break;

      case 'L':
      {
          if( nIndexValue * 4 + 4 > nDataSize )
          {
              CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
              return false;
          }
          GInt32 nNumber = 0;
          memcpy(&nNumber, pabyData + nIndexValue * 4, 4);
          HFAStandard(4, &nNumber);
          nIntRet = nNumber;
          dfDoubleRet = nIntRet;
      }
      break;

      case 'f':
      {
          if( nIndexValue * 4 + 4 > nDataSize )
          {
              CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
              return false;
          }
          float fNumber = 0.0f;
          memcpy(&fNumber, pabyData + nIndexValue * 4, 4);
          HFAStandard(4, &fNumber);
          if( static_cast<double>(fNumber) > std::numeric_limits<int>::max() ||
              static_cast<double>(fNumber) < std::numeric_limits<int>::min() ||
              CPLIsNan(fNumber) )
          {
              CPLError(CE_Failure, CPLE_AppDefined,
                       "Too large for int: %f", fNumber);
              return false;
          }
          dfDoubleRet = fNumber;
          nIntRet = static_cast<int>(fNumber);
      }
      break;

      case 'd':
      {
          if( nIndexValue * 8 + 8 > nDataSize )
          {
              CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
              return false;
          }
          double dfNumber = 0;
          memcpy(&dfNumber, pabyData + nIndexValue * 8, 8);
          HFAStandard(8, &dfNumber);
          dfDoubleRet = dfNumber;
          if( dfNumber > std::numeric_limits<int>::max() ||
              dfNumber < std::numeric_limits<int>::min() ||
              CPLIsNan(dfNumber) )
          {
              CPLError(CE_Failure, CPLE_AppDefined,
                       "Too large for int: %f", dfNumber);
              return false;
          }
          nIntRet = static_cast<int>(dfNumber);
      }
      break;

      case 'b':
      {
          if( nDataSize < 12 )
              return false;

          GInt32 nRows = 0;
          memcpy(&nRows, pabyData, 4);
          HFAStandard(4, &nRows);

          GInt32 nColumns = 0;
          memcpy(&nColumns, pabyData + 4, 4);
          HFAStandard(4, &nColumns);

          GInt16 nBaseItemType = 0;
          memcpy(&nBaseItemType, pabyData + 8, 2);
          HFAStandard(2, &nBaseItemType);
          // We ignore the 2 byte objecttype value.

          if( nIndexValue < -3 ||
              nRows <= 0 ||
              nColumns <= 0 ||
              nRows > INT_MAX / nColumns ||
              nIndexValue >= nRows * nColumns )
              return false;

          pabyData += 12;
          nDataSize -= 12;

          if( nIndexValue == -3 )
          {
              dfDoubleRet = nBaseItemType;
              nIntRet = nBaseItemType;
          }
          else if( nIndexValue == -2 )
          {
              dfDoubleRet = nColumns;
              nIntRet = nColumns;
          }
          else if( nIndexValue == -1 )
          {
              dfDoubleRet = nRows;
              nIntRet = nRows;
          }
          else if( nBaseItemType == EPT_u1 )
          {
              // TODO(schwehr): What are these constants like 8 and 0x7?
              if( nIndexValue * 8 >= nDataSize )
              {
                  CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
                  return false;
              }

              if( pabyData[nIndexValue >> 3] & (1 << (nIndexValue & 0x7)) )
              {
                  dfDoubleRet = 1;
                  nIntRet = 1;
              }
              else
              {
                  dfDoubleRet = 0.0;
                  nIntRet = 0;
              }
          }
          else if( nBaseItemType == EPT_u2 )
          {
              const int nBitOffset = nIndexValue & 0x3;
              const int nByteOffset = nIndexValue >> 2;

              if( nByteOffset >= nDataSize )
              {
                  CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
                  return false;
              }

              const int nMask = 0x3;
              nIntRet = (pabyData[nByteOffset] >> nBitOffset) & nMask;
              dfDoubleRet = nIntRet;
          }
          else if( nBaseItemType == EPT_u4 )
          {
              const int nBitOffset = nIndexValue & 0x7;
              const int nByteOffset = nIndexValue >> 3;

              if( nByteOffset >= nDataSize )
              {
                  CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
                  return false;
              }

              const int nMask = 0x7;
              nIntRet = (pabyData[nByteOffset] >> nBitOffset) & nMask;
              dfDoubleRet = nIntRet;
          }
          else if( nBaseItemType == EPT_u8 )
          {
              if( nIndexValue >= nDataSize )
              {
                  CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
                  return false;
              }
              dfDoubleRet = pabyData[nIndexValue];
              nIntRet = pabyData[nIndexValue];
          }
          else if( nBaseItemType == EPT_s8 )
          {
              if( nIndexValue >= nDataSize )
              {
                  CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
                  return false;
              }
              dfDoubleRet = ((signed char *)pabyData)[nIndexValue];
              nIntRet = ((signed char *)pabyData)[nIndexValue];
          }
          else if( nBaseItemType == EPT_s16 )
          {
              if( nIndexValue * 2 + 2 > nDataSize )
              {
                  CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
                  return false;
              }
              GInt16 nValue = 0;
              memcpy(&nValue, pabyData + 2 * nIndexValue, 2);
              HFAStandard(2, &nValue);

              dfDoubleRet = nValue;
              nIntRet = nValue;
          }
          else if( nBaseItemType == EPT_u16 )
          {
              if( nIndexValue * 2 + 2 > nDataSize )
              {
                  CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
                  return false;
              }
              GUInt16 nValue = 0;
              memcpy(&nValue, pabyData + 2 * nIndexValue, 2);
              HFAStandard(2, &nValue);

              dfDoubleRet = nValue;
              nIntRet = nValue;
          }
          else if( nBaseItemType == EPT_s32 )
          {
              if( nIndexValue * 4 + 4 > nDataSize)
              {
                  CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
                  return false;
              }
              GInt32 nValue = 0;
              memcpy(&nValue, pabyData + 4 * nIndexValue, 4);
              HFAStandard(4, &nValue);

              dfDoubleRet = nValue;
              nIntRet = nValue;
          }
          else if( nBaseItemType == EPT_u32 )
          {
              if( nIndexValue * 4 + 4 > nDataSize )
              {
                  CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
                  return false;
              }
              GUInt32 nValue = 0;
              memcpy(&nValue, pabyData + 4 * nIndexValue, 4);
              HFAStandard(4, &nValue);

              dfDoubleRet = nValue;
              nIntRet = nValue;
          }
          else if( nBaseItemType == EPT_f32 )
          {
              if( nIndexValue * 4 + 4 > nDataSize )
              {
                  CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
                  return false;
              }
              float fValue = 0.0f;
              memcpy(&fValue, pabyData + 4 * nIndexValue, 4);
              HFAStandard(4, &fValue);

              dfDoubleRet = fValue;
              nIntRet = FloatToIntClamp(fValue);
          }
          else if( nBaseItemType == EPT_f64 )
          {
              if( nIndexValue * 8 + 8 > nDataSize )
              {
                  CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
                  return false;
              }
              double dfValue = 0.0;
              memcpy(&dfValue, pabyData + 8 * nIndexValue, 8);
              HFAStandard(8, &dfValue);

              dfDoubleRet = dfValue;
              const int nMax = std::numeric_limits<int>::max();
              const int nMin = std::numeric_limits<int>::min();
              if( dfDoubleRet >= nMax )
              {
                  nIntRet = nMax;
              }
              else if( dfDoubleRet <= nMin )
              {
                  nIntRet = nMin;
              }
              else if( CPLIsNan(dfDoubleRet) )
              {
                  CPLError(CE_Warning, CPLE_AppDefined,
                           "NaN converted to INT_MAX.");
                  nIntRet = nMax;
              }
              else
              {
                  nIntRet = static_cast<int>(dfDoubleRet);
              }
          }
          else
          {
              CPLError(CE_Failure, CPLE_AppDefined,
                       "Unknown base item type: %d", nBaseItemType);
              return false;
          }
      }
      break;

      case 'o':
        if( poItemObjectType != nullptr )
        {
            int nExtraOffset = 0;

            if( poItemObjectType->nBytes > 0 )
            {
                if( nIndexValue != 0 &&
                    poItemObjectType->nBytes > INT_MAX / nIndexValue )
                    // TODO(schwehr): Why was this CE_Failure when the others
                    // are false?
                    return false;
                nExtraOffset = poItemObjectType->nBytes * nIndexValue;
            }
            else
            {
                for( int iIndexCounter = 0;
                     iIndexCounter < nIndexValue && nExtraOffset < nDataSize;
                     iIndexCounter++ )
                {
                    std::set<HFAField*> oVisitedFields;
                    const int nInc =
                        poItemObjectType->GetInstBytes(
                            pabyData + nExtraOffset,
                            nDataSize - nExtraOffset,
                            oVisitedFields);
                    if( nInc <= 0 || nExtraOffset > INT_MAX - nInc )
                    {
                        CPLError(CE_Failure, CPLE_AppDefined,
                                 "Invalid return value");
                        // TODO(schwehr): Verify this false is okay.
                        return false;
                    }

                    nExtraOffset += nInc;
                }
            }

            if( nExtraOffset >= nDataSize )
                return false;

            pabyRawData = pabyData + nExtraOffset;

            if( pszField != nullptr && strlen(pszField) > 0 )
            {
                return
                    poItemObjectType->
                        ExtractInstValue(pszField, pabyRawData,
                                          nDataOffset + nExtraOffset,
                                          nDataSize - nExtraOffset,
                                          chReqType, pReqReturn,
                                          pnRemainingDataSize);
            }
        }
        else
        {
            // E. Rouault: not completely sure about this, but helps avoid
            // DoS timeouts in cases like
            return false;
        }
        break;

      default:
        return false;
        break;
    }

    // Return the appropriate representation.
    if( chReqType == 's' )
    {
        if( pszStringRet == nullptr )
        {
            // HFAEntry:: BuildEntryFromMIFObject() expects to have always 8
            // bytes before the data. In normal situations, it should not go
            // here, but that can happen if the file is corrupted so reserve the
            // first 8 bytes before the string to contain null bytes.
            memset(szNumberString, 0, 8);
            CPLsnprintf(szNumberString + 8, sizeof(szNumberString) - 8,
                        "%.14g", dfDoubleRet);
            pszStringRet = szNumberString + 8;
        }

        *((char **)pReqReturn) = pszStringRet;
        return true;
    }
    else if( chReqType == 'd' )
    {
        *((double *)pReqReturn) = dfDoubleRet;
        return true;
    }
    else if( chReqType == 'i' )
    {
        *((int *)pReqReturn) = nIntRet;
        return true;
    }
    else if( chReqType == 'p' )
    {
        *((GByte **)pReqReturn) = pabyRawData;
        return true;
    }
    else
    {
        CPLAssert(false);
        return false;
    }
}

/************************************************************************/
/*                            GetInstBytes()                            */
/*                                                                      */
/*      Get the number of bytes in a particular instance of a           */
/*      field.  This will normally be the fixed internal nBytes         */
/*      value, but for pointer objects will include the variable        */
/*      portion.                                                        */
/************************************************************************/

int HFAField::GetInstBytes( GByte *pabyData, int nDataSize,
                            std::set<HFAField*>& oVisitedFields )

{
    if( oVisitedFields.find(this) != oVisitedFields.end() )
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Recursion detected");
        return -1;
    }

    if( nBytes > -1 )
        return nBytes;

    int nCount = 1;
    int nInstBytes = 0;

    if( chPointer != '\0' )
    {
        if( nDataSize < 4 )
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
            return -1;
        }

        memcpy(&nCount, pabyData, 4);
        HFAStandard(4, &nCount);

        pabyData += 8;
        nInstBytes += 8;
    }

    if( chItemType == 'b' && nCount != 0 )  // BASEDATA
    {
        if( nDataSize - nInstBytes < 4 + 4 + 2 )
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
            return -1;
        }

        GInt32 nRows = 0;
        memcpy(&nRows, pabyData, 4);
        HFAStandard(4, &nRows);
        GInt32 nColumns = 0;
        memcpy(&nColumns, pabyData + 4, 4);
        HFAStandard(4, &nColumns);
        GInt16 nBaseItemType = 0;
        memcpy(&nBaseItemType, pabyData + 8, 2);
        HFAStandard(2, &nBaseItemType);
        if( nBaseItemType < EPT_MIN || nBaseItemType > EPT_MAX )
            return -1;

        EPTType eBaseItemType = static_cast<EPTType>(nBaseItemType);

        nInstBytes += 12;

        if( nRows < 0 || nColumns < 0 )
            return -1;
        if( nColumns != 0 && nRows > INT_MAX / nColumns )
            return -1;
        if( nRows != 0 &&
            ((HFAGetDataTypeBits(eBaseItemType) + 7) / 8) > INT_MAX / nRows )
            return -1;
        if( nColumns != 0 &&
            ((HFAGetDataTypeBits(eBaseItemType) + 7) / 8) * nRows >
            INT_MAX / nColumns )
            return -1;
        if( ((HFAGetDataTypeBits(eBaseItemType) + 7) / 8) * nRows * nColumns >
            INT_MAX - nInstBytes )
            return -1;

        nInstBytes +=
            ((HFAGetDataTypeBits(eBaseItemType) + 7) / 8) * nRows * nColumns;
    }
    else if( poItemObjectType == nullptr )
    {
        if( nCount != 0 &&
            HFADictionary::GetItemSize(chItemType) > INT_MAX / nCount )
            return -1;
        if( nCount * HFADictionary::GetItemSize(chItemType) >
            INT_MAX - nInstBytes )
            return -1;
        nInstBytes += nCount * HFADictionary::GetItemSize(chItemType);
    }
    else
    {
        oVisitedFields.insert(this);
        for( int i = 0;
             i < nCount && nInstBytes < nDataSize && nInstBytes >= 0;
             i++ )
        {
            const int nThisBytes =
                poItemObjectType->GetInstBytes(pabyData,
                                               nDataSize - nInstBytes,
                                               oVisitedFields);
            if( nThisBytes <= 0 || nInstBytes > INT_MAX - nThisBytes )
            {
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid return value");
                return -1;
            }

            nInstBytes += nThisBytes;
            pabyData += nThisBytes;
        }
        oVisitedFields.erase(this);
    }

    return nInstBytes;
}

/************************************************************************/
/*                            GetInstCount()                            */
/*                                                                      */
/*      Get the count for a particular instance of a field.  This       */
/*      will normally be the built in value, but for variable fields    */
/*      this is extracted from the data itself.                         */
/************************************************************************/

int HFAField::GetInstCount( GByte * pabyData, int nDataSize ) const

{
    if( chPointer == '\0' )
        return nItemCount;

    if( chItemType == 'b' )
    {
        if( nDataSize < 20 )
            return 0;

        GInt32 nRows = 0;
        memcpy(&nRows, pabyData + 8, 4);
        HFAStandard(4, &nRows);
        GInt32 nColumns = 0;
        memcpy(&nColumns, pabyData + 12, 4);
        HFAStandard(4, &nColumns);

        if( nRows < 0 || nColumns < 0 )
            return 0;
        if( nColumns != 0 && nRows > INT_MAX / nColumns )
            return 0;

        return nRows * nColumns;
    }

    if( nDataSize < 4 )
        return 0;

    GInt32 nCount = 0;
    memcpy(&nCount, pabyData, 4);
    HFAStandard(4, &nCount);
    return nCount;
}

/************************************************************************/
/*                           DumpInstValue()                            */
/************************************************************************/

void HFAField::DumpInstValue( FILE *fpOut,
                              GByte *pabyData,
                              GUInt32 nDataOffset, int nDataSize,
                              const char *pszPrefix )

{
    const int nEntries = GetInstCount(pabyData, nDataSize);

    // Special case for arrays of chars or uchars which are printed
    // as a string.
    if( (chItemType == 'c' || chItemType == 'C') && nEntries > 0 )
    {
        void *pReturn = nullptr;
        if( ExtractInstValue(nullptr, 0,
                             pabyData, nDataOffset, nDataSize,
                             's', &pReturn) )
            CPL_IGNORE_RET_VAL(
                VSIFPrintf(fpOut, "%s%s = `%s'\n",
                           pszPrefix, pszFieldName,
                           static_cast<char *>(pReturn)));
        else
            CPL_IGNORE_RET_VAL(
                VSIFPrintf(fpOut, "%s%s = (access failed)\n",
                           pszPrefix, pszFieldName));

        return;
    }

    // For BASEDATA objects, we want to first dump their dimension and type.
    if( chItemType == 'b' )
    {
        int nDataType = 0;
        const bool bSuccess =
            ExtractInstValue(nullptr, -3, pabyData, nDataOffset,
                             nDataSize, 'i', &nDataType);
        if( bSuccess )
        {
            int nColumns = 0;
            ExtractInstValue(nullptr, -2, pabyData, nDataOffset,
                             nDataSize, 'i', &nColumns);
            int nRows = 0;
            ExtractInstValue(nullptr, -1, pabyData, nDataOffset,
                             nDataSize, 'i', &nRows);
            CPL_IGNORE_RET_VAL(VSIFPrintf(
                fpOut, "%sBASEDATA(%s): %dx%d of %s\n",
                pszPrefix, pszFieldName,
                nColumns, nRows,
                (nDataType >= EPT_MIN && nDataType <= EPT_MAX)
                ? HFAGetDataTypeName(static_cast<EPTType>(nDataType))
                : "invalid type"));
        }
        else
        {
            CPL_IGNORE_RET_VAL(VSIFPrintf(fpOut, "%sBASEDATA(%s): empty\n",
                                          pszPrefix, pszFieldName));
        }
    }

    // Dump each entry in the field array.
    void *pReturn = nullptr;

    const int nMaxEntry = std::min(MAX_ENTRY_REPORT, nEntries);
    for( int iEntry = 0; iEntry < nMaxEntry; iEntry++ )
    {
        if( nEntries == 1 )
            CPL_IGNORE_RET_VAL(
                VSIFPrintf(fpOut, "%s%s = ", pszPrefix, pszFieldName));
        else
            CPL_IGNORE_RET_VAL(
                VSIFPrintf(fpOut, "%s%s[%d] = ",
                           pszPrefix, pszFieldName, iEntry));

        switch( chItemType )
        {
          case 'f':
          case 'd':
          {
              double dfValue = 0.0;
              if( ExtractInstValue(nullptr, iEntry,
                                    pabyData, nDataOffset, nDataSize,
                                    'd', &dfValue) )
                  CPL_IGNORE_RET_VAL(VSIFPrintf(fpOut, "%f\n", dfValue));
              else
                  CPL_IGNORE_RET_VAL(VSIFPrintf(fpOut, "(access failed)\n"));
          }
          break;

          case 'b':
          {
              double dfValue = 0.0;

              if( ExtractInstValue(nullptr, iEntry,
                                    pabyData, nDataOffset, nDataSize,
                                    'd', &dfValue) )
                  CPL_IGNORE_RET_VAL(
                      VSIFPrintf(fpOut, "%s%.15g\n", pszPrefix, dfValue));
              else
                  CPL_IGNORE_RET_VAL(
                      VSIFPrintf(fpOut, "%s(access failed)\n", pszPrefix));
          }
          break;

          case 'e':
            if( ExtractInstValue(nullptr, iEntry,
                                  pabyData, nDataOffset, nDataSize,
                                  's', &pReturn) )
                CPL_IGNORE_RET_VAL(VSIFPrintf(fpOut, "%s\n",
                                               (char *) pReturn));
            else
                CPL_IGNORE_RET_VAL(VSIFPrintf(fpOut, "(access failed)\n"));
            break;

          case 'o':
            if( !ExtractInstValue(nullptr, iEntry,
                                   pabyData, nDataOffset, nDataSize,
                                   'p', &pReturn) )
            {
                CPL_IGNORE_RET_VAL(VSIFPrintf(fpOut, "(access failed)\n"));
            }
            else
            {
                CPL_IGNORE_RET_VAL(VSIFPrintf(fpOut, "\n"));

                const int nByteOffset =
                    static_cast<int>(((GByte *) pReturn) - pabyData);

                char szLongFieldName[256] = {};
                snprintf(szLongFieldName, sizeof(szLongFieldName),
                          "%s    ", pszPrefix);

                if( poItemObjectType )
                    poItemObjectType->DumpInstValue(fpOut,
                                                    pabyData + nByteOffset,
                                                    nDataOffset + nByteOffset,
                                                    nDataSize - nByteOffset,
                                                    szLongFieldName);
            }
            break;

          default:
          {
              GInt32 nIntValue = 0;

              if( ExtractInstValue(nullptr, iEntry,
                                   pabyData, nDataOffset, nDataSize,
                                   'i', &nIntValue) )
                  CPL_IGNORE_RET_VAL(VSIFPrintf(fpOut, "%d\n", nIntValue));
              else
                  CPL_IGNORE_RET_VAL(VSIFPrintf(fpOut, "(access failed)\n"));
          }
          break;
        }
    }

    if( nEntries > MAX_ENTRY_REPORT )
        CPL_IGNORE_RET_VAL(VSIFPrintf(
            fpOut, "%s ... remaining instances omitted ...\n", pszPrefix));

    if( nEntries == 0 )
        CPL_IGNORE_RET_VAL(
            VSIFPrintf(fpOut, "%s%s = (no values)\n", pszPrefix, pszFieldName));
}


void CClassLoadoutPanel::ClearItemOptionsMenu( void )
{
	SetOptionsButtonText( m_pItemOptionPanel->GetItemSlot(), "+" );
	m_pItemOptionPanel->SetItemSlot( LOADOUT_POSITION_INVALID, m_iCurrentClassIndex );
	m_pItemOptionPanel->SetVisible( false );
}

#include "cbase.h"
#include "class_loadout_panel.h"
#include "c_tf_player.h"
#include "vgui_controls/CheckButton.h"
#include "econ_gcmessages.h"
#include "gc_clientsystem.h"
#include "tf_item_system.h"
#include "loadout_preset_panel.h"
#include "econ_item_description.h"
#include "item_style_select_dialog.h"
#include "vgui/IInput.h"
#include "vgui_controls/PanelListPanel.h"

// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>

extern ConVar tf_respawn_on_loadoutchanges;

ConVar tf_show_preset_explanation_in_class_loadout( "tf_show_preset_explanation_in_class_loadout", "1", FCVAR_HIDDEN | FCVAR_CLIENTDLL | FCVAR_ARCHIVE );
ConVar tf_show_taunt_explanation_in_class_loadout( "tf_show_taunt_explanation_in_class_loadout", "1", FCVAR_HIDDEN | FCVAR_CLIENTDLL | FCVAR_ARCHIVE );

void ParticleSlider_UpdateRequest( int iLoadoutPosition, float value )
{
	CClassLoadoutPanel *pPanel = g_pClassLoadoutPanel;
	if ( !pPanel )
		return;

	CEconItemView *pHat = pPanel->GetItemInSlot( iLoadoutPosition );
	if ( !pHat )
		return;

	// does this hat even have a particle effect
	static CSchemaAttributeDefHandle pAttrDef_AttachParticleEffect( "attach particle effect" );
	uint32 iHasEffect = 0;
	if ( !pHat->FindAttribute( pAttrDef_AttachParticleEffect, &iHasEffect ) )
		return;

	// Check for use head toggle
	static CSchemaAttributeDefHandle pAttrDef_UseHeadOrigin( "particle effect use head origin" );
	uint32 iUseHead = 0;
	if ( !pHat->FindAttribute( pAttrDef_UseHeadOrigin, &iUseHead ) || iUseHead == 0 )
		return;

	// Look for the attribute and request to change it
	static CSchemaAttributeDefHandle pAttrDef_VerticalOffset( "particle effect vertical offset" );
	uint32 iOffSet = 0;
	if ( !pHat->FindAttribute( pAttrDef_VerticalOffset, &iOffSet ) && value == 0 )
	{
		return;
	}
	else
	{
		const float& flAttrValue = (float&)iOffSet;
		if ( value == flAttrValue )
			return; // no change do nothing
	}

	// Send a message to the GC to request a change
	GCSDK::CProtoBufMsg<CMsgSetItemEffectVerticalOffset> msg( k_EMsgGCSetItemEffectVerticalOffset );
	msg.Body().set_item_id( pHat->GetItemID() );
	msg.Body().set_offset( value );
	GCClientSystem()->BSendMessage( msg );
}

void HatOffset_Callback( IConVar *pConVar, char const *pOldString, float flOldValue )
{
	ConVarRef cVarRef( pConVar );
	ParticleSlider_UpdateRequest( LOADOUT_POSITION_HEAD, cVarRef.GetFloat() );
}
ConVar tf_hat_effect_offset( "tf_hat_effect_offset", "0", FCVAR_DEVELOPMENTONLY, "Adjust the position of the unusual effect for your hat.", HatOffset_Callback );

void Misc1Offset_Callback( IConVar *pConVar, char const *pOldString, float flOldValue )
{
	ConVarRef cVarRef( pConVar );
	ParticleSlider_UpdateRequest( LOADOUT_POSITION_MISC, cVarRef.GetFloat() );
}
ConVar tf_misc1_effect_offset( "tf_misc1_effect_offset", "0", FCVAR_DEVELOPMENTONLY, "Adjust the position of the unusual effect for your hat.", Misc1Offset_Callback );

void Misc2Offset_Callback( IConVar *pConVar, char const *pOldString, float flOldValue )
{
	ConVarRef cVarRef( pConVar );
	ParticleSlider_UpdateRequest( LOADOUT_POSITION_MISC2, cVarRef.GetFloat() );
}
ConVar tf_misc2_effect_offset( "tf_misc2_effect_offset", "0", FCVAR_DEVELOPMENTONLY, "Adjust the position of the unusual effect for your hat.", Misc2Offset_Callback );

// Hacky solution to different classes wanting different slots visible in their loadouts, and in different positions
struct LoadoutPanelPositioningInstance
{
	int m_iPos[NUM_ITEM_PANELS_IN_LOADOUT];
};

bool IsTauntPanelPosition( int iButtonPos )
{
	return iButtonPos >= 9 && iButtonPos <= 16;
}

const LoadoutPanelPositioningInstance g_DefaultLoadoutPanelPositioning =
{
	{
		1,	// LOADOUT_POSITION_PRIMARY = 0,
		2,	// LOADOUT_POSITION_SECONDARY,
		3,	// LOADOUT_POSITION_MELEE,
		0,	// LOADOUT_POSITION_UTILITY,  // STAGING ONLY
		0,	// LOADOUT_POSITION_BUILDING,
		0,	// LOADOUT_POSITION_PDA,
		0,	// LOADOUT_POSITION_PDA2,
		5,	// LOADOUT_POSITION_HEAD,
		6,	// LOADOUT_POSITION_MISC,
		8,	// LOADOUT_POSITION_ACTION,
		7,	// LOADOUT_POSITION_MISC2,
		9,	// LOADOUT_POSITION_TAUNT,
		10,	// LOADOUT_POSITION_TAUNT2,
		11,	// LOADOUT_POSITION_TAUNT3,
		12,	// LOADOUT_POSITION_TAUNT4,
		13,	// LOADOUT_POSITION_TAUNT5,
		14,	// LOADOUT_POSITION_TAUNT6,
		15,	// LOADOUT_POSITION_TAUNT7,
		16,	// LOADOUT_POSITION_TAUNT8,

#ifdef STAGING_ONLY
		0,	// LOADOUT_POSITION_PDA_ADDON1,
		0,	// LOADOUT_POSITION_PDA_ADDON2,
		0,  // LOADOUT_POSITION_PDA3,
		0,	// LOADOUT_POSITION_BUILDING2,
#endif // STAGING_ONLY
	}
};

const LoadoutPanelPositioningInstance g_LoadoutPanelPositioning_Spy =
{
	{
		0,	// LOADOUT_POSITION_PRIMARY = 0,
		1,	// LOADOUT_POSITION_SECONDARY,
		2,	// LOADOUT_POSITION_MELEE,
		0,	// LOADOUT_POSITION_UTILITY,  // STAGING ONLY
		4,	// LOADOUT_POSITION_BUILDING,		// sapper
		0,	// LOADOUT_POSITION_PDA,			// disguise kit (Hidden)
		3,	// LOADOUT_POSITION_PDA2,			// Watch
		5,	// LOADOUT_POSITION_HEAD,
		6,	// LOADOUT_POSITION_MISC,
		8,	// LOADOUT_POSITION_ACTION,
		7,	// LOADOUT_POSITION_MISC2,
		9,	// LOADOUT_POSITION_TAUNT,
		10,	// LOADOUT_POSITION_TAUNT2,
		11,	// LOADOUT_POSITION_TAUNT3,
		12,	// LOADOUT_POSITION_TAUNT4,
		13,	// LOADOUT_POSITION_TAUNT5,
		14,	// LOADOUT_POSITION_TAUNT6,
		15,	// LOADOUT_POSITION_TAUNT7,
		16,	// LOADOUT_POSITION_TAUNT8,
#ifdef STAGING_ONLY
		0,	// LOADOUT_POSITION_PDA_ADDON1,
		0,	// LOADOUT_POSITION_PDA_ADDON2,
		0,  // LOADOUT_POSITION_PDA3,
		0,	// LOADOUT_POSITION_BUILDING2,
#endif // STAGING_ONLY
	}
};

const LoadoutPanelPositioningInstance g_LoadoutPanelPositioning_Engineer =
{
	{
		1,	// LOADOUT_POSITION_PRIMARY = 0,
		2,	// LOADOUT_POSITION_SECONDARY,
		3,	// LOADOUT_POSITION_MELEE,
		0,	// LOADOUT_POSITION_UTILITY,  // STAGING ONLY
		0,	// LOADOUT_POSITION_BUILDING,
		4,	// LOADOUT_POSITION_PDA,
		0,	// LOADOUT_POSITION_PDA2,
		5,	// LOADOUT_POSITION_HEAD,
		6,	// LOADOUT_POSITION_MISC,
		8,	// LOADOUT_POSITION_ACTION,
		7,	// LOADOUT_POSITION_MISC2,
		9,	// LOADOUT_POSITION_TAUNT,
		10,	// LOADOUT_POSITION_TAUNT2,
		11,	// LOADOUT_POSITION_TAUNT3,
		12,	// LOADOUT_POSITION_TAUNT4,
		13,	// LOADOUT_POSITION_TAUNT5,
		14,	// LOADOUT_POSITION_TAUNT6,
		15,	// LOADOUT_POSITION_TAUNT7,
		16,	// LOADOUT_POSITION_TAUNT8,
#ifdef STAGING_ONLY
		17,	// LOADOUT_POSITION_PDA_ADDON1,
		18,	// LOADOUT_POSITION_PDA_ADDON2,
		0,  // LOADOUT_POSITION_PDA3,
		0,	// LOADOUT_POSITION_BUILDING2,
#endif // STAGING_ONLY
	}
};

const LoadoutPanelPositioningInstance *g_VisibleLoadoutSlotsPerClass[] =
{
	&g_DefaultLoadoutPanelPositioning,			// TF_CLASS_UNDEFINED
	&g_DefaultLoadoutPanelPositioning,			// TF_CLASS_SCOUT
	&g_DefaultLoadoutPanelPositioning,			// TF_CLASS_SNIPER
	&g_DefaultLoadoutPanelPositioning,			// TF_CLASS_SOLDIER
	&g_DefaultLoadoutPanelPositioning,			// TF_CLASS_DEMOMAN
	&g_DefaultLoadoutPanelPositioning,			// TF_CLASS_MEDIC
	&g_DefaultLoadoutPanelPositioning,			// TF_CLASS_HEAVYWEAPONS
	&g_DefaultLoadoutPanelPositioning,			// TF_CLASS_PYRO
	&g_LoadoutPanelPositioning_Spy,				// TF_CLASS_SPY
	&g_LoadoutPanelPositioning_Engineer,		// TF_CLASS_ENGINEER
};

COMPILE_TIME_ASSERT( ARRAYSIZE( g_VisibleLoadoutSlotsPerClass ) == TF_LAST_NORMAL_CLASS );

// Particle Effect Slider
CLoadoutItemOptionsPanel::CLoadoutItemOptionsPanel( Panel *parent, const char *pName ) : vgui::EditablePanel( parent, pName )
{
	m_pHatParticleSlider = NULL;
	m_pHatParticleUseHeadButton = NULL;

	m_iCurrentClassIndex = -1;
	m_eItemSlot = LOADOUT_POSITION_INVALID;

	m_pListPanel = new vgui::PanelListPanel( this, "PanelListPanel" );
	m_pListPanel->SetFirstColumnWidth( 0 );
	m_pHatParticleSlider = new CCvarSlider( m_pListPanel, "HatParticleSlider" );
	m_pHatParticleSlider->AddActionSignalTarget( this );
	m_pHatParticleUseHeadButton = new vgui::CheckButton( m_pListPanel, "HatUseHeadCheckButton", "#GameUI_ParticleHatUseHead" );
	m_pHatParticleUseHeadButton->AddActionSignalTarget( this );
	m_pSetStyleButton = new CExButton( m_pListPanel, "SetStyleButton", "#TF_Item_SelectStyle" );
	m_pSetStyleButton->AddActionSignalTarget( this );
}

void CLoadoutItemOptionsPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
{
	BaseClass::ApplySchemeSettings( pScheme );
	LoadControlSettings( "resource/UI/ItemOptionsPanel.res" );
}

void CLoadoutItemOptionsPanel::PerformLayout( void )
{
	BaseClass::PerformLayout();
	m_pHatParticleSlider->SetTickColor( Color( 235, 226, 202, 255 ) ); // tanlight
}

void CLoadoutItemOptionsPanel::OnCommand( const char *command )
{
	if ( FStrEq( command, "particle_button_clicked" ) )
	{
		UpdateItemOptionsUI();
		return;
	}
	else if ( FStrEq( command, "particle_use_head_clicked" ) )
	{
		// Grab current hat
		CEconItemView *pHat = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, m_eItemSlot );
		if ( !pHat )
			return;

		// does this hat even have a particle effect
		static CSchemaAttributeDefHandle pAttrDef_AttachParticleEffect( "attach particle effect" );
		uint32 iHasEffect = 0;
		if ( !pHat->FindAttribute( pAttrDef_AttachParticleEffect, &iHasEffect ) )
			return;

		// Send a message to the GC to request a change
		GCSDK::CProtoBufMsg<CMsgSetHatEffectUseHeadOrigin> msg( k_EMsgGCSetHatEffectUseHeadOrigin );
		msg.Body().set_item_id( pHat->GetItemID() );
		msg.Body().set_use_head( m_pHatParticleUseHeadButton->IsSelected() );
		GCClientSystem()->BSendMessage( msg );
		return;
	}
	else if ( FStrEq( command, "set_style" ) )
	{
		CEconItemView *pHat = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, m_eItemSlot );
		CStyleSelectDialog *pStyle = vgui::SETUP_PANEL( new CStyleSelectDialog( GetParent(), pHat ) );
		if ( pStyle )
		{
			pStyle->Show();
		}
	}
}

void CLoadoutItemOptionsPanel::OnMessage( const KeyValues* pParams, vgui::VPANEL hFromPanel )
{
	if ( FStrEq( pParams->GetName(), "SliderDragEnd" ) )
	{
		m_pHatParticleSlider->ApplyChanges();
	}

	BaseClass::OnMessage( pParams, hFromPanel );
}

void CLoadoutItemOptionsPanel::SetItemSlot( loadout_positions_t eItemSlot, int iClassIndex )
{
	m_eItemSlot = eItemSlot;
	m_iCurrentClassIndex = iClassIndex;
	// Init the Slider based on the slot
	const char * pszConVarName = NULL;

	switch ( eItemSlot )
	{
	case LOADOUT_POSITION_HEAD :
		pszConVarName = "tf_hat_effect_offset";
		break;
	case LOADOUT_POSITION_MISC :
		pszConVarName = "tf_misc1_effect_offset";
		break;
	case LOADOUT_POSITION_MISC2 :
		pszConVarName = "tf_misc2_effect_offset";
		break;
	default:
		break;
	}

	if ( pszConVarName )
	{
		m_pHatParticleSlider->SetupSlider( -8, 8, pszConVarName, false );
	}

	m_pHatParticleSlider->SetTickColor( Color( 235, 226, 202, 255 ) ); // tanlight
	m_pHatParticleSlider->SetTickCaptions( "", "" );

	UpdateItemOptionsUI();
}

void CLoadoutItemOptionsPanel::UpdateItemOptionsUI()
{
	if ( m_eItemSlot == LOADOUT_POSITION_INVALID )
		return;

	m_pListPanel->RemoveAll();

	// Add controls for various item options
	AddControlsParticleEffect();
	AddControlsSetStyle();

	// Bail if no controls added
	if ( m_pListPanel->GetItemCount() == 0 )
	{
		// We should have some controls if we get to this point.
		Assert( 0 );
		SetVisible( false );
		return;
	}

	// Resize the background and list panel to contain all the controls
	int nVertPixels = m_pListPanel->ComputeVPixelsNeeded();
	int nNewTall = Min( 200, nVertPixels );
	m_pListPanel->SetTall( nNewTall );
	SetTall( nNewTall );
	InvalidateLayout( true, false );
}

void CLoadoutItemOptionsPanel::AddControlsParticleEffect( void ) const
{
	m_pHatParticleUseHeadButton->SetVisible( false );
	m_pHatParticleSlider->SetVisible( false );

	CEconItemView *pItem = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, m_eItemSlot );
	if ( pItem )
	{
		// does this hat even have a particle effect
		static CSchemaAttributeDefHandle pAttrDef_AttachParticleEffect( "attach particle effect" );
		uint32 iValue = 0;
		if ( pItem->FindAttribute( pAttrDef_AttachParticleEffect, &iValue ) )
		{
			m_pHatParticleUseHeadButton->SetVisible( true );
			m_pListPanel->AddItem( NULL, m_pHatParticleUseHeadButton );
			m_pListPanel->AddItem( NULL, m_pHatParticleSlider );

			// Check for use head toggle
			static CSchemaAttributeDefHandle pAttrDef_UseHeadOrigin( "particle effect use head origin" );
			uint32 iUseHead = 0;
			if ( pItem->FindAttribute( pAttrDef_UseHeadOrigin, &iUseHead ) && iUseHead > 0 )
			{
				m_pHatParticleSlider->SetVisible( true );

				m_pHatParticleUseHeadButton->SetSelected( true );
				m_pHatParticleSlider->SetTickColor( Color( 235, 226, 202, 255 ) ); // tanlight
				m_pHatParticleSlider->Repaint();

				// Get offset if it exists
				static CSchemaAttributeDefHandle pAttrDef_VerticalOffset( "particle effect vertical offset" );
				uint32 iOffset = 0;
				if ( pItem->FindAttribute( pAttrDef_VerticalOffset, &iOffset ) )
				{
					m_pHatParticleSlider->SetSliderValue( (float&)iOffset );
				}
			}
			else
			{
				m_pHatParticleUseHeadButton->SetSelected( false );
			}
		}
	}
}

void CLoadoutItemOptionsPanel::AddControlsSetStyle( void ) const
{
	m_pSetStyleButton->SetVisible( false );

	CEconItemView *pItem = GetItem();
	if ( pItem && pItem->GetStaticData()->GetNumStyles() )
	{
		m_pSetStyleButton->SetVisible( true );
		m_pListPanel->AddItem( NULL, m_pSetStyleButton );
	}
}

CEconItemView* CLoadoutItemOptionsPanel::GetItem( void ) const
{
	return TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, m_eItemSlot );
}

CClassLoadoutPanel *g_pClassLoadoutPanel = NULL;
CClassLoadoutPanel::CClassLoadoutPanel( vgui::Panel *parent )
	: CBaseLoadoutPanel( parent, "class_loadout_panel" )
	, m_pItemOptionPanelKVs( NULL )
{
	m_iCurrentClassIndex = TF_CLASS_UNDEFINED;
	m_iCurrentTeamIndex = TF_TEAM_RED;
	m_iCurrentSlotIndex = -1;
	m_pPlayerModelPanel = NULL;
	m_pSelectionPanel = NULL;
	m_pTauntHintLabel = NULL;
	m_pTauntLabel = NULL;
	m_pTauntCaratLabel = NULL;
	m_pPassiveAttribsLabel = NULL;
	m_pLoadoutPresetPanel = NULL;
	m_pPresetsExplanationPopup = NULL;
	m_pTauntsExplanationPopup = NULL;
	m_pBuildablesButton = NULL;

	m_pCharacterLoadoutButton = NULL;
	m_pTauntLoadoutButton = NULL;

	m_bInTauntLoadoutMode = false;

	g_pClassLoadoutPanel = this;

	m_pItemOptionPanel = new CLoadoutItemOptionsPanel( this, "ItemOptionsPanel" );
}

CClassLoadoutPanel::~CClassLoadoutPanel()
{
	if ( m_pItemOptionPanelKVs )
	{
		m_pItemOptionPanelKVs->deleteThis();
		m_pItemOptionPanelKVs = NULL;
	}
}

void CClassLoadoutPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
{
	LoadControlSettings( "Resource/UI/ClassLoadoutPanel.res" );

	BaseClass::ApplySchemeSettings( pScheme );

	m_pPlayerModelPanel = dynamic_cast<CTFPlayerModelPanel*>( FindChildByName("classmodelpanel") );
	m_pTauntHintLabel = dynamic_cast<vgui::Label*>( FindChildByName("TauntHintLabel") );
	m_pTauntLabel = dynamic_cast<CExLabel*>( FindChildByName("TauntLabel") );
	m_pTauntCaratLabel = dynamic_cast<CExLabel*>( FindChildByName("TauntCaratLabel") );
	m_pBuildablesButton = dynamic_cast<CExButton*>( FindChildByName("BuildablesButton") );
	m_pCharacterLoadoutButton = dynamic_cast<CExImageButton*>( FindChildByName("CharacterLoadoutButton") );
	m_pTauntLoadoutButton = dynamic_cast<CExImageButton*>( FindChildByName("TauntLoadoutButton") );
	m_pPassiveAttribsLabel = dynamic_cast<CExLabel*>( FindChildByName("PassiveAttribsLabel") );
	m_pLoadoutPresetPanel = dynamic_cast<CLoadoutPresetPanel*>( FindChildByName( "loadout_preset_panel" ) );
	m_pPresetsExplanationPopup = dynamic_cast<CExplanationPopup*>( FindChildByName( "PresetsExplanation" ) );
	m_pTauntsExplanationPopup = dynamic_cast<CExplanationPopup*>( FindChildByName( "TauntsExplanation" ) );
	m_pTopLinePanel = FindChildByName( "TopLine" );
	if ( m_pPassiveAttribsLabel )
	{
		m_pPassiveAttribsLabel->SetMouseInputEnabled( false );
	}

	m_pMouseOverTooltip->SetPositioningStrategy( IPTTP_BOTTOM_SIDE );

	m_aDefaultColors[LOADED][FG][DEFAULT] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetDefaultColorFg", Color( 255, 255, 255, 255 ) );
	m_aDefaultColors[LOADED][FG][ARMED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetArmedColorFg", Color( 255, 255, 255, 255 ) );
	m_aDefaultColors[LOADED][FG][DEPRESSED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetDepressedColorFg", Color( 255, 255, 255, 255 ) );

	m_aDefaultColors[LOADED][BG][DEFAULT] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetDefaultColorBg", Color( 255, 255, 255, 255 ) );
	m_aDefaultColors[LOADED][BG][ARMED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetArmedColorBg", Color( 255, 255, 255, 255 ) );
	m_aDefaultColors[LOADED][BG][DEPRESSED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetDepressedColorBg", Color( 255, 255, 255, 255 ) );

	m_aDefaultColors[NOTLOADED][FG][DEFAULT] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.TextColor", Color( 255, 255, 255, 255 ) );
	m_aDefaultColors[NOTLOADED][FG][ARMED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.ArmedTextColor", Color( 255, 255, 255, 255 ) );
	m_aDefaultColors[NOTLOADED][FG][DEPRESSED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.DepressedTextColor", Color( 255, 255, 255, 255 ) );

	m_aDefaultColors[NOTLOADED][BG][DEFAULT] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.BgColor", Color( 255, 255, 255, 255 ) );
	m_aDefaultColors[NOTLOADED][BG][ARMED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.ArmedBgColor", Color( 255, 255, 255, 255 ) );
	m_aDefaultColors[NOTLOADED][BG][DEPRESSED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.DepressedBgColor", Color( 255, 255, 255, 255 ) );
}

void CClassLoadoutPanel::ApplySettings( KeyValues *inResourceData )
{
	BaseClass::ApplySettings( inResourceData );

	KeyValues *pItemKV = inResourceData->FindKey( "itemoptionpanels_kv" );
	if ( pItemKV )
	{
		if ( m_pItemOptionPanelKVs )
		{
			m_pItemOptionPanelKVs->deleteThis();
		}
		m_pItemOptionPanelKVs = new KeyValues("itemoptionpanels_kv");
		pItemKV->CopySubkeys( m_pItemOptionPanelKVs );
	}

#ifdef STAGING_ONLY
	// PDA Panels
	if ( m_pItemModelPanels.Count() > LOADOUT_POSITION_PDA_ADDON2 )
	{
		for ( int i = LOADOUT_POSITION_PDA_ADDON1; i <= LOADOUT_POSITION_PDA_ADDON2; i++ )
		{
			int wide = m_pItemModelPanels[i]->GetWide();
			int tall = m_pItemModelPanels[i]->GetTall();

			m_pItemModelPanels[i]->SetSize( wide / 2, tall / 2 );
			m_pItemModelPanels[i]->InvalidateLayout();
		}
	}
#endif
}

void CClassLoadoutPanel::PerformLayout( void )
{
	BaseClass::PerformLayout();

	// This is disabled by default in res file. IF we turn it on again, uncomment this.
	/*if ( m_pPassiveAttribsLabel )
	{
		m_pPassiveAttribsLabel->SetVisible( !m_bInTauntLoadoutMode );
	}*/

	if ( m_pTauntHintLabel )
	{
		m_pTauntHintLabel->SetVisible( m_bInTauntLoadoutMode );

		const char *key = engine->Key_LookupBinding( "taunt" );
		if ( !key )
		{
			key = "< not bound >";
		}
		SetDialogVariable( "taunt", key );
	}

	if ( m_pTauntLabel )
	{
		m_pTauntLabel->SetVisible( m_bInTauntLoadoutMode );
	}
	if ( m_pTauntCaratLabel )
	{
		m_pTauntCaratLabel->SetVisible( m_bInTauntLoadoutMode );
	}
	if ( m_pCharacterLoadoutButton )
	{
		UpdatePageButtonColor( m_pCharacterLoadoutButton, !m_bInTauntLoadoutMode );
	}
	if ( m_pTauntLoadoutButton )
	{
		UpdatePageButtonColor( m_pTauntLoadoutButton, m_bInTauntLoadoutMode );
	}

	FOR_EACH_VEC( m_vecItemOptionButtons, i )
	{
		m_vecItemOptionButtons[i]->SetVisible( false );
	}

	for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
	{
		// Viewing a class loadout. Layout the buttons & the class image.
		if ( i >= NUM_ITEM_PANELS_IN_LOADOUT )
		{
			m_pItemModelPanels[i]->SetVisible( false );
			continue;
		}

		int iButtonPos = 0;
		if ( m_iCurrentClassIndex != TF_CLASS_UNDEFINED )
		{
			iButtonPos = g_VisibleLoadoutSlotsPerClass[m_iCurrentClassIndex]->m_iPos[i];
		}

		bool bIsVisible = false;
		if ( iButtonPos > 0 )
		{
			bIsVisible = m_bInTauntLoadoutMode ? IsTauntPanelPosition( iButtonPos ) : !IsTauntPanelPosition( iButtonPos );
		}
		m_pItemModelPanels[i]->SetVisible( bIsVisible );

		if ( bIsVisible )
		{
			if ( m_bInTauntLoadoutMode )
			{
				iButtonPos -= g_VisibleLoadoutSlotsPerClass[m_iCurrentClassIndex]->m_iPos[LOADOUT_POSITION_TAUNT];
			}
			else
			{
				iButtonPos--;
			}

#ifdef STAGING_ONLY
			// Override for the PDA AddOnSlots
			if ( i == LOADOUT_POSITION_PDA_ADDON1 )
			{
				int iYPos, iXPos;
				m_pItemModelPanels[ LOADOUT_POSITION_PDA ]->GetPos( iXPos, iYPos );
				int iWide, iTall;
				m_pItemModelPanels[ LOADOUT_POSITION_PDA ]->GetSize( iWide, iTall );

				m_pItemModelPanels[i]->SetPos( iXPos + iWide + XRES(1), iYPos );
				m_pItemModelPanels[i]->SetSize( iWide / 2.1, iTall / 2.1 );
				continue;
			}
			else if ( i == LOADOUT_POSITION_PDA_ADDON2 )
			{
				int iYPos, iXPos;
				m_pItemModelPanels[LOADOUT_POSITION_PDA]->GetPos( iXPos, iYPos );
				int iWide, iTall;
				m_pItemModelPanels[LOADOUT_POSITION_PDA]->GetSize( iWide, iTall );

				m_pItemModelPanels[i]->SetPos( iXPos + iWide + XRES(1), iYPos + iTall - (iTall / 2.1 ) );
				m_pItemModelPanels[i]->SetSize( iWide / 2.1, iTall / 2.1 );
				continue;
			}
#endif

			m_pItemModelPanels[i]->SetNoItemText( ItemSystem()->GetItemSchema()->GetLoadoutStringsForDisplay( EEquipType_t::EQUIP_TYPE_CLASS )[i] );

			int iCenter = GetWide() * 0.5;
			int iColumnHeight = 4;
			int iColumn = iButtonPos / iColumnHeight;
			int iYButtonPos = iButtonPos % iColumnHeight;

			int iOffset = iColumn == 0 ? m_iItemXPosOffcenterA : m_iItemXPosOffcenterB + ((iColumn - 1) * 200);
			int	iXPos = iCenter + iOffset;
			int	iYPos = m_iItemYPos + (m_iItemYDelta * iYButtonPos);
			m_pItemModelPanels[i]->SetPos( iXPos, iYPos );

			// Update position and visibility of the item option buttons
			if ( i < m_vecItemOptionButtons.Count() )
			{
				// Place the button just inside the item model panel
				CExButton* pItemOptionsPanel = m_vecItemOptionButtons[iButtonPos];
				int iButtonWide = m_pItemModelPanels[i]->GetWide();
				int iMyWide = pItemOptionsPanel->GetWide();
				int iOptionsXPos = iColumn == 0
								 ? iXPos + iButtonWide - iMyWide
								 : iXPos;
				pItemOptionsPanel->SetPos( iOptionsXPos, iYPos );

				CEconItemView *pItemData = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, i );
				// Enable or disable the item options button for this item model panel
				pItemOptionsPanel->SetVisible( !m_bInTauntLoadoutMode && AnyOptionsAvailableForItem( pItemData ) );
				pItemOptionsPanel->SetCommand( CFmtStr( "options%d", i ) );
			}
		}

		m_pItemModelPanels[ i ]->SetSelected( false );
	}

	if ( m_pLoadoutPresetPanel )
	{
		m_pLoadoutPresetPanel->SetPos( ( ScreenWidth() - m_pLoadoutPresetPanel->GetWide() ) / 2, m_iItemYPos );
	}

	LinkModelPanelControllerNavigation( true );
}

void CClassLoadoutPanel::OnKeyCodePressed( vgui::KeyCode code )
{
	// See if the preset control uses this key
	if( m_pLoadoutPresetPanel->HandlePresetKeyPressed( code ) )
	{
		return;
	}

	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 )
	{
		// just eat all navigation keys so we don't
		// end up with undesirable navigation behavior bubbling from
		// one item model panel to another
	}
	else if( nButtonCode == KEY_XBUTTON_A || code == KEY_ENTER || nButtonCode == STEAMCONTROLLER_A )
	{
		// show the current loadout slot
		int nSelected = GetFirstSelectedItemIndex( true );
		if( nSelected != -1 )
		{
			OnCommand( VarArgs("change%d", nSelected ) );
		}
	}
	else
	{
		BaseClass::OnKeyCodePressed( code );
	}
}

void CClassLoadoutPanel::OnNavigateTo( const char* panelName )
{
	CItemModelPanel *pChild = dynamic_cast<CItemModelPanel *>( FindChildByName( panelName ) );
	if( !pChild )
		return;

	pChild->SetSelected( true );
	SetBorderForItem( pChild, false );
	pChild->RequestFocus();
}

void CClassLoadoutPanel::OnNavigateFrom( const char* panelName )
{
	CItemModelPanel *pChild = dynamic_cast<CItemModelPanel *>( FindChildByName( panelName ) );
	if( !pChild )
		return;

	pChild->SetSelected( false );
	SetBorderForItem( pChild, false );
}

void CClassLoadoutPanel::OnShowPanel( bool bVisible, bool bReturningFromArmory )
{
	if ( bVisible )
	{
		// always start in character loadout page
		SetLoadoutPage( CHARACTER_LOADOUT_PAGE );

		if ( m_pSelectionPanel )
		{
			m_pSelectionPanel->SetVisible( false );
			m_pSelectionPanel->MarkForDeletion();
			m_pSelectionPanel = NULL;
		}

		m_iCurrentSlotIndex = TF_WPN_TYPE_PRIMARY;
		if( m_pItemModelPanels.Count() && m_pItemModelPanels[0] )
		{
			m_pItemModelPanels[0]->SetSelected( true );
			SetBorderForItem( m_pItemModelPanels[0], false );
		}

		m_bLoadoutHasChanged = false;

		if ( tf_show_preset_explanation_in_class_loadout.GetBool() && m_pPresetsExplanationPopup )
		{
			m_pPresetsExplanationPopup->Popup();
			tf_show_preset_explanation_in_class_loadout.SetValue( 0 );
		}
		else if ( tf_show_taunt_explanation_in_class_loadout.GetBool() && m_pTauntsExplanationPopup )
		{
			m_pTauntsExplanationPopup->Popup();
			tf_show_taunt_explanation_in_class_loadout.SetValue( 0 );
		}

		ClearItemOptionsMenu();
	}
	else
	{
		if ( m_pPlayerModelPanel )
		{
			m_pPlayerModelPanel->ClearCarriedItems();
		}
	}
}
void CClassLoadoutPanel::PostShowPanel( bool bVisible )
{
	if ( bVisible )
	{
		if ( m_pPlayerModelPanel )
		{
			m_pPlayerModelPanel->SetVisible( true );
		}

		if ( m_pBuildablesButton )
		{
			m_pBuildablesButton->SetVisible( m_iCurrentClassIndex == TF_CLASS_ENGINEER );
		}
	}
}

void CClassLoadoutPanel::SetClass( int iClass )
{
	m_iCurrentClassIndex = iClass;

	if ( m_pLoadoutPresetPanel )
	{
		m_pLoadoutPresetPanel->SetClass( m_iCurrentClassIndex );
	}
}

void CClassLoadoutPanel::SetTeam( int iTeam )
{
	Assert( IsValidTFTeam( iTeam ) );
	m_iCurrentTeamIndex = iTeam;
}

int CClassLoadoutPanel::GetNumRelevantSlots() const
{
	return m_pItemModelPanels.Count();
}

CEconItemView *CClassLoadoutPanel::GetItemInSlot( int iSlot )
{
	if( iSlot >= 0 && iSlot < m_pItemModelPanels.Count() )
	{
		return m_pItemModelPanels[iSlot]->GetItem();
	}

	return NULL;
}

void CClassLoadoutPanel::FireGameEvent( IGameEvent *event )
{
	// If we're not visible, ignore all events
	if ( !IsVisible() )
		return;

	BaseClass::FireGameEvent( event );

	// We need to update ourselves after the base has done it, so our item models have been updated
	const char *type = event->GetName();
	if ( Q_strcmp( "inventory_updated", type ) == 0 )
	{
		if ( m_pPlayerModelPanel )
		{
			m_pPlayerModelPanel->HoldItemInSlot( m_iCurrentSlotIndex );
		}
	}
}

void CClassLoadoutPanel::AddNewItemPanel( int iPanelIndex )
{
	BaseClass::AddNewItemPanel( iPanelIndex );

	m_vecItemOptionButtons[ m_vecItemOptionButtons.AddToTail() ] = new CExButton( this,
																				  CFmtStr( "item_options_button%d", iPanelIndex ),
																				  "+",
																				  this,
																				  CFmtStr( "options%d", iPanelIndex ) );
}

void CClassLoadoutPanel::UpdateModelPanels( void )
{
	// Search for a Robot Costume
	bool bIsRobot = false;
	static CSchemaAttributeDefHandle pAttrDef_PlayerRobot( "appear as mvm robot" );
	// For now, fill them out with the local player's currently wielded items
	for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
	{
		CEconItemView *pItemData = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, i );
		if ( !pItemData )
			continue;
		if ( FindAttribute( pItemData, pAttrDef_PlayerRobot ) )
		{
			bIsRobot = true;
			break;
		}
	}

	// We're showing the loadout for a specific class.
	TFPlayerClassData_t *pData = GetPlayerClassData( m_iCurrentClassIndex );
	if ( m_pPlayerModelPanel )
	{
		m_pPlayerModelPanel->ClearCarriedItems();
		m_pPlayerModelPanel->SetToPlayerClass( m_iCurrentClassIndex, bIsRobot );
		m_pPlayerModelPanel->SetTeam( m_iCurrentTeamIndex );
	}

	// For now, fill them out with the local player's currently wielded items
	for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
	{
		CEconItemView *pItemData = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, i );
		m_pItemModelPanels[i]->SetItem( pItemData );
		m_pItemModelPanels[i]->SetShowQuantity( true );
		m_pItemModelPanels[i]->SetSelected( false );
		SetBorderForItem( m_pItemModelPanels[i], false );

		if ( m_pPlayerModelPanel && pItemData && pItemData->IsValid() )
		{
			m_pPlayerModelPanel->AddCarriedItem( pItemData );
		}
	}

	if ( m_pPlayerModelPanel )
	{
		m_pPlayerModelPanel->HoldItemInSlot( m_iCurrentSlotIndex );
	}

	SetDialogVariable( "loadoutclass", g_pVGuiLocalize->Find( pData->m_szLocalizableName ) );

	UpdatePassiveAttributes();

	// Now layout again to position our item buttons
	InvalidateLayout();

	if ( m_pItemOptionPanel->IsVisible() )
	{
		m_pItemOptionPanel->UpdateItemOptionsUI();
	}
}

void CClassLoadoutPanel::OnItemPanelMouseReleased( vgui::Panel *panel )
{
	CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );

	if ( pItemPanel && IsVisible() )
	{
		for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
		{
			if ( m_pItemModelPanels[i] == pItemPanel  )
			{
				OnCommand( VarArgs("change%d", i) );
				return;
			}
		}
	}
}

void CClassLoadoutPanel::OnSelectionReturned( KeyValues *data )
{
	if ( data )
	{
		uint64 ulIndex = data->GetUint64( "itemindex", INVALID_ITEM_ID );

		// ulIndex implies do nothing (escape key)
		if ( ulIndex != 0 )
		{
			TFInventoryManager()->EquipItemInLoadout( m_iCurrentClassIndex, m_iCurrentSlotIndex, ulIndex );

			m_bLoadoutHasChanged = true;

			UpdateModelPanels();

			// Send the preset panel a msg so it can save the change
			KeyValues *pLoadoutChangedMsg = new KeyValues( "LoadoutChanged" );
			pLoadoutChangedMsg->SetInt( "slot", m_iCurrentSlotIndex );
			pLoadoutChangedMsg->SetUint64( "itemid", ulIndex );
			PostMessage( m_pLoadoutPresetPanel, pLoadoutChangedMsg );
		}
	}

	PostMessage( GetParent(), new KeyValues("SelectionEnded") );

	// It'll have deleted itself, so we don't need to clean it up
	m_pSelectionPanel = NULL;
	OnCancelSelection();

	// find the selected item and give it the focus
	CItemModelPanel *pSelection = GetFirstSelectedItemModelPanel( true );
	if( !pSelection )
	{
		m_pItemModelPanels[0]->SetSelected( true );
		pSelection = m_pItemModelPanels[0];
	}

	pSelection->RequestFocus();
}

void CClassLoadoutPanel::OnCancelSelection( void )
{
	if ( m_pSelectionPanel )
	{
		m_pSelectionPanel->SetVisible( false );
		m_pSelectionPanel->MarkForDeletion();
		m_pSelectionPanel = NULL;
	}

	if ( m_pPlayerModelPanel )
	{
		m_pPlayerModelPanel->SetVisible( true );
	}
}

void CClassLoadoutPanel::RespawnPlayer()
{
	if ( tf_respawn_on_loadoutchanges.GetBool() )
	{
		// Tell the GC to tell server that we should respawn if we're in a respawn room
		GCSDK::CGCMsg< MsgGCEmpty_t > msg( k_EMsgGCRespawnPostLoadoutChange );
		GCClientSystem()->BSendMessage( msg );
	}
}

// Purpose: Apply KVs to the item option buttons
void CClassLoadoutPanel::ApplyKVsToItemPanels( void )
{
	BaseClass::ApplyKVsToItemPanels();

	if ( m_pItemOptionPanelKVs )
	{
		for ( int i = 0; i < m_vecItemOptionButtons.Count(); i++ )
		{
			m_vecItemOptionButtons[i]->ApplySettings( m_pItemOptionPanelKVs );
			m_vecItemOptionButtons[i]->InvalidateLayout();
		}
	}
}

void CClassLoadoutPanel::OnClosing( void )
{
	if ( m_pPlayerModelPanel )
	{
		m_pPlayerModelPanel->ClearCarriedItems();
	}

	if ( m_bLoadoutHasChanged )
	{
		RespawnPlayer();

		m_bLoadoutHasChanged = false;
	}
}

extern const char *g_szItemBorders[AE_MAX_TYPES][5];
extern ConVar cl_showbackpackrarities;
void CClassLoadoutPanel::SetBorderForItem( CItemModelPanel *pItemPanel, bool bMouseOver )
{
	if ( !pItemPanel )
		return;

	const char *pszBorder = NULL;

	if ( pItemPanel->IsGreyedOut() )
	{
		pszBorder = "EconItemBorder";
	}
	else
	{
		int iRarity = 0;
		if ( pItemPanel->HasItem() && cl_showbackpackrarities.GetBool() )
		{
			iRarity = pItemPanel->GetItem()->GetItemQuality();

			uint8 nRarity = pItemPanel->GetItem()->GetItemDefinition()->GetRarity();
			if ( ( nRarity != k_unItemRarity_Any ) && ( iRarity != AE_SELFMADE ) )
			{
				// translate this quality to rarity
				iRarity = nRarity + AE_RARITY_DEFAULT;
			}

			if ( iRarity > 0 )
			{
				if ( bMouseOver || pItemPanel->IsSelected() )
				{
					pszBorder = g_szItemBorders[iRarity][1];
				}
				else
				{
					pszBorder = g_szItemBorders[iRarity][0];
				}
			}
		}

		if ( iRarity == 0 )
		{
			if ( bMouseOver || pItemPanel->IsSelected() )
			{
				pszBorder = "LoadoutItemMouseOverBorder";
			}
			else
			{
				pszBorder = "EconItemBorder";
			}
		}
	}

	vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() );
	pItemPanel->SetBorder( pScheme->GetBorder( pszBorder ) );
}

// Purpose: Clear the item options menu and reset the button that summoned it

// Purpose: Safely set the text for a button
void CClassLoadoutPanel::SetOptionsButtonText( int nIndex, const char* pszText )
{
	if ( nIndex >= 0 && nIndex < m_vecItemOptionButtons.Count() )
	{
		m_vecItemOptionButtons[ m_pItemOptionPanel->GetItemSlot() ]->SetText( pszText );
	}
}

// Purpose: Return if the passed in item has any options
bool CClassLoadoutPanel::AnyOptionsAvailableForItem( const CEconItemView *pItem )
{
	if ( !pItem )
		return false;

	if ( pItem->GetStaticData()->GetNumSelectableStyles() > 1 )
		return true;

	// Unusual particle effect! For Cosmetics only
	static CSchemaAttributeDefHandle pAttrDef_AttachParticleEffect( "attach particle effect" );
	if ( pItem->FindAttribute( pAttrDef_AttachParticleEffect ) && pItem->GetItemDefinition()->GetLoadoutSlot( 0 ) >= LOADOUT_POSITION_HEAD )
		return true;

	return false;
}

void CClassLoadoutPanel::SetLoadoutPage( classloadoutpage_t loadoutPage )
{
	ClearItemOptionsMenu();
	switch ( loadoutPage )
	{
		case CHARACTER_LOADOUT_PAGE:
		{
			m_bInTauntLoadoutMode = false;
		}
		break;
		case TAUNT_LOADOUT_PAGE:
		{
			m_bInTauntLoadoutMode = true;
		}
		break;
		default:
		{
			// Unhandled loadout page
			Assert( 0 );
		}
	}
	InvalidateLayout();
}

void CClassLoadoutPanel::OnCommand( const char *command )
{
	if ( FStrEq( command, "characterloadout" ) )
	{
		SetLoadoutPage( CHARACTER_LOADOUT_PAGE );
		return;
	}
	else if ( FStrEq( command, "tauntloadout" ) )
	{
		SetLoadoutPage( TAUNT_LOADOUT_PAGE );
		return;
	}
	else if ( !V_strnicmp( command, "change", 6 ) )
	{
		const char *pszNum = command+6;
		if ( pszNum && pszNum[0] )
		{
			int iSlot = atoi(pszNum);
			if ( iSlot >= 0 && iSlot < CLASS_LOADOUT_POSITION_COUNT && m_iCurrentClassIndex != TF_CLASS_UNDEFINED )
			{
				if ( m_iCurrentSlotIndex != iSlot )
				{
					m_iCurrentSlotIndex = iSlot;
				}

				// Create the selection screen. It removes itself on close.
				m_pSelectionPanel = new CEquipSlotItemSelectionPanel( this, m_iCurrentClassIndex, iSlot );
				m_pSelectionPanel->ShowPanel( 0, true );

				if ( m_pPlayerModelPanel )
				{
					m_pPlayerModelPanel->SetVisible( false );
				}

				ClearItemOptionsMenu();

				PostMessage( GetParent(), new KeyValues("SelectionStarted") );
			}
		}

		return;
	}
	else if ( !V_strnicmp( command, "options", 7 ) )
	{
		const char *pszNum = command + 7;
		if( pszNum && pszNum[0] )
		{
			int iSlot = atoi( pszNum );
			if ( iSlot >= 0 && iSlot < m_vecItemOptionButtons.Count() && m_iCurrentClassIndex != TF_CLASS_UNDEFINED )
			{
				// Change the button we're coming from to be a "+"
				SetOptionsButtonText( m_pItemOptionPanel->GetItemSlot(), "+" );

				// Update the current slot index for callback from the setstyle button.
				// It will send us a message to change the item the player model is holding
				// and we need this to be updated for that.
				m_iCurrentSlotIndex = iSlot;

				// Did they just toggle?
				if ( m_pItemOptionPanel->GetItemSlot() == iSlot )
				{
					m_pItemOptionPanel->SetVisible( !m_pItemOptionPanel->IsVisible() );
				}
				else
				{
					// Set the options panel to have the data for this slot
					m_pItemOptionPanel->SetItemSlot( (loadout_positions_t)iSlot, m_iCurrentClassIndex );
					m_pItemOptionPanel->SetVisible( true );
					// Figure out if this is on the left or right
					int iColumnHeight = 4;
					int iColumn = iSlot / iColumnHeight;
					PinCorner_e myCornerToPin = iColumn == 0 ? PIN_TOPLEFT : PIN_TOPRIGHT;
					PinCorner_e siblingCornerPinTo = iColumn == 0 ? PIN_TOPRIGHT : PIN_TOPLEFT;
					// Pin to the appropriate side
					int iButtonPos = g_VisibleLoadoutSlotsPerClass[m_iCurrentClassIndex]->m_iPos[ iSlot ] - 1;
					m_pItemOptionPanel->PinToSibling( m_vecItemOptionButtons[ iButtonPos ]->GetName(), myCornerToPin, siblingCornerPinTo );
					m_pItemOptionPanel->UpdateItemOptionsUI();
				}

				// Change the button we're going to to be "-" if we're visible, "+" if we're not
				SetOptionsButtonText( m_pItemOptionPanel->GetItemSlot(), m_pItemOptionPanel->IsVisible() ? "-" : "+" );
			}
			return;
		}
	}
	BaseClass::OnCommand( command );
}

void CClassLoadoutPanel::OnMessage( const KeyValues* pParams, vgui::VPANEL hFromPanel )
{
	BaseClass::OnMessage( pParams, hFromPanel );
}

struct passive_attrib_to_print_t
{
	const CEconItemAttributeDefinition *m_pAttrDef;
	attrib_value_t m_value;
};

class CAttributeIterator_AddPassiveAttribsToPassiveList : public CEconItemSpecificAttributeIterator
{
public:
	CAttributeIterator_AddPassiveAttribsToPassiveList( CUtlVector<passive_attrib_to_print_t> *pList, bool bForceAdd )
		: m_pList( pList )
		, m_bForceAdd( bForceAdd )
	{
		Assert( m_pList );
	}

	virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, attrib_value_t value )
	{
		Assert( pAttrDef );

		if ( pAttrDef->IsHidden() )
			return true;

		if ( !m_bForceAdd )
		{
			const char *pDesc = pAttrDef->GetArmoryDescString();
			if ( !pDesc || !pDesc[0] )
				return true;

			// If we have the "on_wearer" key, we're a passive attribute
			if ( !Q_stristr(pDesc, "on_wearer") )
				return true;
		}

		// Now see if we're already in the list
		FOR_EACH_VEC( (*m_pList), i )
		{
			passive_attrib_to_print_t& passiveAttr = (*m_pList)[i];

			Assert( passiveAttr.m_pAttrDef );

			// We match if our class is the same -- this is a case-sensitive compare!
			if ( Q_strcmp( passiveAttr.m_pAttrDef->GetAttributeClass(), pAttrDef->GetAttributeClass() ) )
				continue;

			// We've found a matching attribute. Collate our values and stomp over the earlier value.
			passiveAttr.m_value = CollateAttributeValues( passiveAttr.m_pAttrDef, passiveAttr.m_value, pAttrDef, value );

			return true;
		}

		// We didn't find it. Add it to the list.
		passive_attrib_to_print_t newPassiveAttr = { pAttrDef, value };
		m_pList->AddToTail( newPassiveAttr );

		return true;
	}

	// Other types are ignored.

private:
	CUtlVector<passive_attrib_to_print_t> *m_pList;
	bool m_bForceAdd;
};

void CClassLoadoutPanel::UpdatePassiveAttributes( void )
{
	if ( !m_pPassiveAttribsLabel )
		return;

	// We build a list of attributes & associated values by looping through all equipped items.
	// This way we can identify & collate attributes based on the same definition index.
	CUtlVector<passive_attrib_to_print_t> vecAttribsToPrint;

	// Loop through all equipped items
	for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
	{
		CEconItemView *pItemData = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, i );
		if ( pItemData && pItemData->IsValid() )
		{
			CAttributeIterator_AddPassiveAttribsToPassiveList attrItPassives( &vecAttribsToPrint, false );
			pItemData->IterateAttributes( &attrItPassives );
		}
	}

	// Then add any set bonuses
	if ( steamapicontext && steamapicontext->SteamUser() )
	{
		CSteamID localSteamID = steamapicontext->SteamUser()->GetSteamID();
		CUtlVector<const CEconItemSetDefinition *> pActiveSets;
		TFInventoryManager()->GetActiveSets( &pActiveSets, localSteamID, m_iCurrentClassIndex );

		FOR_EACH_VEC( pActiveSets, set )
		{
			CAttributeIterator_AddPassiveAttribsToPassiveList attrItSetPassives( &vecAttribsToPrint, true );
			pActiveSets[set]->IterateAttributes( &attrItSetPassives );
		}
	}

	// Now build the text
	wchar_t wszPassiveDesc[4096];
	wszPassiveDesc[0] = '\0';
	m_pPassiveAttribsLabel->GetTextImage()->ClearColorChangeStream();

	wchar_t *pHeader = g_pVGuiLocalize->Find( "#TF_PassiveAttribs" );
	if ( pHeader )
	{
		V_wcscpy_safe( wszPassiveDesc, pHeader );
		V_wcscat_safe( wszPassiveDesc, L"\n" );
	}

	if ( vecAttribsToPrint.Count() )
	{
		FOR_EACH_VEC( vecAttribsToPrint, i )
		{
			CEconAttributeDescription AttrDesc( GLocalizationProvider(), vecAttribsToPrint[i].m_pAttrDef, vecAttribsToPrint[i].m_value );
			AddAttribPassiveText( AttrDesc, wszPassiveDesc, ARRAYSIZE(wszPassiveDesc) );
		}
	}
	else
	{
		vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() );
		Color col = pScheme->GetColor( GetColorNameForAttribColor( ATTRIB_COL_NEUTRAL ), Color(255,255,255,255) );
		m_pPassiveAttribsLabel->GetTextImage()->AddColorChange( col, Q_wcslen( wszPassiveDesc ) );

		wchar_t *pNone = g_pVGuiLocalize->Find( "#TF_PassiveAttribs_None" );
		if ( pNone )
		{
			V_wcscat_safe( wszPassiveDesc, pNone );
		}
	}

	m_pPassiveAttribsLabel->SetText( wszPassiveDesc );
}

void CClassLoadoutPanel::AddAttribPassiveText( const CEconAttributeDescription& AttrDesc, INOUT_Z_CAP(iNumPassiveChars) wchar_t *out_wszPassiveDesc, int iNumPassiveChars )
{
	vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() );
	Assert( pScheme );

	if ( !AttrDesc.GetDescription().IsEmpty() )
	{
		// Insert the color change at the current position
		Color col = pScheme->GetColor( GetColorNameForAttribColor( AttrDesc.GetDefaultColor() ), Color(255,255,255,255) );
		m_pPassiveAttribsLabel->GetTextImage()->AddColorChange( col, Q_wcslen( out_wszPassiveDesc ) );

		// Now append the text of the attribute
		V_wcsncat( out_wszPassiveDesc, AttrDesc.GetDescription().Get(), iNumPassiveChars );
		V_wcsncat( out_wszPassiveDesc, L"\n", iNumPassiveChars );
	}
}

void CClassLoadoutPanel::UpdatePageButtonColor( CExImageButton *pPageButton, bool bIsActive )
{
	if ( pPageButton )
	{
		int iLoaded = bIsActive ? LOADED : NOTLOADED;
		pPageButton->SetDefaultColor( m_aDefaultColors[iLoaded][FG][DEFAULT], m_aDefaultColors[iLoaded][BG][DEFAULT] );
		pPageButton->SetArmedColor( m_aDefaultColors[iLoaded][FG][ARMED], m_aDefaultColors[iLoaded][BG][ARMED] );
		pPageButton->SetDepressedColor( m_aDefaultColors[iLoaded][FG][DEPRESSED], m_aDefaultColors[iLoaded][BG][DEPRESSED] );
	}
}