Example #1
0
void APIServiceManager::_CloseXMLHeader(uint32 cacheStyle)
{
    switch( cacheStyle )
    {
        case EVEAPI::CacheStyles::Long:
            // 2 hour cache timer
            _BuildSingleXMLTag( "cachedUntil", Win32TimeToString(Win32TimeNow() + 120*Win32Time_Minute).c_str() );
            break;
        case EVEAPI::CacheStyles::Short:
            // 5 minute cache timer
            _BuildSingleXMLTag( "cachedUntil", Win32TimeToString(Win32TimeNow() + 5*Win32Time_Minute).c_str() );
            break;
        case EVEAPI::CacheStyles::Modified:
            // 15 minute cache timer
            _BuildSingleXMLTag( "cachedUntil", Win32TimeToString(Win32TimeNow() + 15*Win32Time_Minute).c_str() );
            break;
    }
}
Example #2
0
const char *PyLookupResolver::LookupInt(uint64 value) const {
    //hackish check for win32 time looking things...
    if(value > 127900000000000000LL && value < 130000000000000000LL) {
        //this is not thread safe or reentrant..
        m_dateBuffer = Win32TimeToString(value);
        return(m_dateBuffer.c_str());
    }

    if(value > 0xFFFFFFFFLL || value < MIN_RESOLVABLE_INT)
        return NULL;
    std::map<uint32, std::string>::const_iterator res;
    res = m_intRecords.find(uint32(value));
    if(res == m_intRecords.end())
        return NULL;
    return(res->second.c_str());
}
Example #3
0
void TimeToString( const Seperator& cmd )
{
    const char* cmdName = cmd.arg( 0 ).c_str();

    if( 1 == cmd.argCount() )
    {
        sLog.Error( cmdName, "Usage: %s win32-time [win32-time] ...", cmdName );
        return;
    }

    for( size_t i = 1; i < cmd.argCount(); ++i )
    {
        const std::string& timeStr = cmd.arg( i );

        uint64 t;
        sscanf( timeStr.c_str(), I64u, &t );
        const std::string time = Win32TimeToString( t );

        sLog.Log( cmdName, "%s is %s.", timeStr.c_str(), time.c_str() );
    }
}
Example #4
0
void APIServiceManager::_BuildXMLHeader()
{
    // Build header at beginning of XML document, so clear existing xml document
    _XmlDoc.Clear();
    // object pointed to by '_pXmlDocOuterTag' is automatically deleted by the TinyXML system with the above call
    if( _pXmlElementStack != NULL )
    {
        delete _pXmlElementStack;
        _pXmlElementStack = NULL;
    }
    _pXmlElementStack = new std::stack<TiXmlElement *>();

    TiXmlDeclaration * decl = new TiXmlDeclaration( "1.0", "UTF-8", "" );
    _XmlDoc.LinkEndChild( decl );

    _pXmlDocOuterTag = new TiXmlElement( "eveapi" );
    _XmlDoc.LinkEndChild( _pXmlDocOuterTag );
    _pXmlDocOuterTag->SetAttribute( "version", "2" );

    TiXmlElement * currentTime = new TiXmlElement( "currentTime" );
    currentTime->LinkEndChild( new TiXmlText( Win32TimeToString(Win32TimeNow()).c_str() ));
    _pXmlDocOuterTag->LinkEndChild( currentTime );
}
Example #5
0
void Character::UpdateSkillQueue()
{
    Client *c = m_factory.entity_list.FindCharacter( itemID() );

    SkillRef currentTraining = GetSkillInTraining();
    if( currentTraining )
    {
        if( m_skillQueue.empty()
            || currentTraining->typeID() != m_skillQueue.front().typeID )
        {
            // either queue is empty or skill with different typeID is in training ...
            // stop training:
            _log( ITEM__ERROR, "%s (%u): Stopping training of skill %s (%u).", itemName().c_str(), itemID(), currentTraining->itemName().c_str(), currentTraining->itemID() );

            /*
            uint64 timeEndTrain = currentTraining->expiryTime();
            if(timeEndTrain != 0)
            {
                double nextLevelSP = currentTraining->GetSPForLevel( currentTraining->skillLevel() + 1 );
                double SPPerMinute = GetSPPerMin( currentTraining );
                double minRemaining = (double)(timeEndTrain - Win32TimeNow()) / (double)Win32Time_Minute;

                currentTraining->Set_skillPoints( nextLevelSP - (minRemaining * SPPerMinute) );
            }

            currentTraining->Clear_expiryTime();
            */

            EvilNumber timeEndTrain = currentTraining->GetAttribute(AttrExpiryTime);
            if (timeEndTrain != 0) {
                EvilNumber nextLevelSP = currentTraining->GetSPForLevel( currentTraining->GetAttribute(AttrSkillLevel) + 1 );
                EvilNumber SPPerMinute = GetSPPerMin( currentTraining );
                EvilNumber minRemaining = (timeEndTrain - EvilNumber(Win32TimeNow())) / (double)Win32Time_Minute;

                //currentTraining->Set_skillPoints( nextLevelSP - (minRemaining * SPPerMinute) );
                EvilNumber skillPointsTrained = nextLevelSP - (minRemaining * SPPerMinute);
                currentTraining->SetAttribute(AttrSkillPoints, skillPointsTrained);
                sLog.Debug( "", "Skill %s (%u) trained %u skill points before termination from training queue", currentTraining->itemName().c_str(), currentTraining->itemID(), skillPointsTrained.get_float() );
            }

            currentTraining->SetAttribute(AttrExpiryTime, 0);

            currentTraining->MoveInto( *this, flagSkill, true );

            if( c != NULL )
            {
                OnSkillTrainingStopped osst;
                osst.itemID = currentTraining->itemID();
                osst.endOfTraining = 0;

                PyTuple* tmp = osst.Encode();
                c->QueueDestinyEvent( &tmp );
                PySafeDecRef( tmp );

                c->UpdateSkillTraining();
            }

            // nothing currently in training
            currentTraining = SkillRef();
        }
    }

    EvilNumber nextStartTime = EvilTimeNow();
    
    while( !m_skillQueue.empty() )
    {
        if( !currentTraining )
        {
            // something should be trained, get desired skill
            uint32 skillTypeID = m_skillQueue.front().typeID;

            currentTraining = GetSkill( skillTypeID );
            if( !currentTraining )
            {
                _log( ITEM__ERROR, "%s (%u): Skill %u to train was not found.", itemName().c_str(), itemID(), skillTypeID );
                break;
            }

            sLog.Debug( "Character::UpdateSkillQueue()", "%s (%u): Starting training of skill %s (%u)",  m_itemName.c_str(), m_itemID, currentTraining->itemName().c_str(), currentTraining->itemID() );

            EvilNumber SPPerMinute = GetSPPerMin( currentTraining );
            EvilNumber NextLevel = currentTraining->GetAttribute(AttrSkillLevel) + 1;
            EvilNumber SPToNextLevel = currentTraining->GetSPForLevel( NextLevel ) - currentTraining->GetAttribute(AttrSkillPoints);
            sLog.Debug( "    ", "Training skill at %f SP/min", SPPerMinute.get_float() );
            sLog.Debug( "    ", "%f SP to next Level of %d", SPToNextLevel.get_float(), NextLevel.get_int() );

            SPPerMinute.to_float();
            SPToNextLevel.to_float();
            nextStartTime.to_float();
            EvilNumber timeTraining = nextStartTime + EvilTime_Minute * SPToNextLevel / SPPerMinute;

            currentTraining->MoveInto( *this, flagSkillInTraining );
            double dbl_timeTraining = timeTraining.get_float() + (double)(Win32Time_Second * 10);
            currentTraining->SetAttribute(AttrExpiryTime, dbl_timeTraining);    // Set server-side
                                                                                // skill expiry + 10 sec

            sLog.Debug( "    ", "Calculated time to complete training = %s", Win32TimeToString((uint64)dbl_timeTraining).c_str() );

            if( c != NULL )
            {
                OnSkillStartTraining osst;
                osst.itemID = currentTraining->itemID();
                osst.endOfTraining = timeTraining.get_float();

                PyTuple* tmp = osst.Encode();
                c->QueueDestinyEvent( &tmp );
                PySafeDecRef( tmp );

                c->UpdateSkillTraining();
            }
        }

        if( currentTraining->GetAttribute(AttrExpiryTime) <= EvilTimeNow() ) {
            // training has been finished:
            sLog.Debug( "Character::UpdateSkillQueue()", "%s (%u): Finishing training of skill %s (%u).", itemName().c_str(), itemID(), currentTraining->itemName().c_str(), currentTraining->itemID() );

            currentTraining->SetAttribute(AttrSkillLevel, currentTraining->GetAttribute(AttrSkillLevel) + 1 );
            currentTraining->SetAttribute(AttrSkillPoints, currentTraining->GetSPForLevel( currentTraining->GetAttribute(AttrSkillLevel) ), true);

            nextStartTime = currentTraining->GetAttribute(AttrExpiryTime);
            currentTraining->SetAttribute(AttrExpiryTime, 0);

            currentTraining->MoveInto( *this, flagSkill, true );

            if( c != NULL )
            {
                OnSkillTrained ost;
                ost.itemID = currentTraining->itemID();

                PyTuple* tmp = ost.Encode();
                c->QueueDestinyEvent( &tmp );
                PySafeDecRef( tmp );

                c->UpdateSkillTraining();
            }

            // erase first element in skill queue
            m_skillQueue.erase( m_skillQueue.begin() );

            // nothing currently in training
            currentTraining = SkillRef();
        }
        // else the skill is in training ...
        else
            break;
    }

    // Re-Calculate total SP trained and store in internal variable:
    _CalculateTotalSPTrained();

    // Save character and skill data:
    SaveCharacter();
    SaveSkillQueue();
}
Example #6
0
std::tr1::shared_ptr<std::string> APIAccountManager::_APIKeyRequest(const APICommandCall * pAPICommandCall)
{
    bool status = false;
    uint32 userID, apiRole;
    std::string username;
    std::string password;
    std::string keyType;
    std::string action;
    std::string apiLimitedKey;
    std::string apiFullKey;
    std::string accountID;
    std::string keyTag;

    sLog.Debug("APIAccountManager::_APIKeyRequest()", "EVEmu API - Account Service Manager - CALL: APIKeyRequest.xml.aspx");

    // 1: Decode arguments:
    if( pAPICommandCall->find( "username" ) != pAPICommandCall->end() )
        username = pAPICommandCall->find( "username" )->second;
    else
    {
        sLog.Error( "APIAccountManager::_APIKeyRequest()", "ERROR: No 'username' parameter found in call argument list - exiting with error" );
        return BuildErrorXMLResponse( "203", "Authentication failure." );
    }

    if( pAPICommandCall->find( "password" ) != pAPICommandCall->end() )
        password = pAPICommandCall->find( "password" )->second;
    else
    {
        sLog.Error( "APIAccountManager::_APIKeyRequest()", "ERROR: No 'password' parameter found in call argument list - exiting with error" );
        return BuildErrorXMLResponse( "203", "Authentication failure." );
    }

    if( pAPICommandCall->find( "keytype" ) != pAPICommandCall->end() )
        keyType = pAPICommandCall->find( "keytype" )->second;
    else
    {
        sLog.Error( "APIAccountManager::_APIKeyRequest()", "ERROR: No 'keytype' parameter found in call argument list - exiting with error" );
        return BuildErrorXMLResponse( "203", "Authentication failure." );
    }

    if( keyType != "full" )
        if( keyType != "limited" )
        {
            sLog.Error( "APIAccountManager::_APIKeyRequest()", "ERROR: 'keytype' parameter has invalid value '%s' - exiting with error", keyType.c_str() );
            return BuildErrorXMLResponse( "203", "Authentication failure." );
        }

    if( pAPICommandCall->find( "action" ) != pAPICommandCall->end() )
        action = pAPICommandCall->find( "action" )->second;
    else
    {
        sLog.Error( "APIAccountManager::_APIKeyRequest()", "ERROR: No 'action' parameter found in call argument list - exiting with error" );
        return BuildErrorXMLResponse( "203", "Authentication failure." );
    }

    if( action != "new" )
        if( action != "get" )
        {
            sLog.Error( "APIAccountManager::_APIKeyRequest()", "ERROR: 'action' parameter has invalid value '%s' - exiting with error", action.c_str() );
            return BuildErrorXMLResponse( "203", "Authentication failure." );
        }

    // 2: Authenticate the username and password against the account table:
    status = _AuthenticateUserNamePassword( username, password );
    if( !status )
    {
        sLog.Error( "APIAccountManager::_APIKeyRequest()", "ERROR: username='******' password='******' does not authenticate.", username.c_str(), password.c_str() );
        return BuildErrorXMLResponse( "203", "Authentication failure." );
    }
    // 2a: Get accountID using username, now that it's been validated:
    status = m_db.GetAccountIdFromUsername( username, &accountID );
    if( !status )
    {
        sLog.Error( "APIAccountManager::_APIKeyRequest()", "ERROR: username='******' cannot be found in 'account' table.", username.c_str() );
        return BuildErrorXMLResponse( "203", "Authentication failure." );
    }

    // 3: Determine if this account's userID exists:
    status = m_db.GetApiAccountInfoUsingAccountID( accountID, &userID, &apiFullKey, &apiLimitedKey, &apiRole );

    // 4: Generate new random 64-character hexadecimal API Keys:
    // Write new API Key to database if key request 'action' is 'new':
    if( !status )
    {
        // 4a: If userID does not exist for this accountID, then insert a new row into the 'accountApi' table regardless of 'get' or 'new':
        apiLimitedKey = _GenerateAPIKey();
        apiFullKey = _GenerateAPIKey();
        status = m_db.InsertNewUserIdApiKeyInfoToDatabase( atol(accountID.c_str()), apiFullKey, apiLimitedKey, EVEAPI::Roles::Player );
    }

    if( action == "new" )
        if( keyType == "limited" )
            apiLimitedKey = _GenerateAPIKey();
        else //if( keyType == "full" )
            apiFullKey = _GenerateAPIKey();

    if( action == "new" )
    {
        if( status )
            // 4b: If userID already exists, generate new API keys and write them back to the database under that userID:
            status = m_db.UpdateUserIdApiKeyDatabaseRow( userID, apiFullKey, apiLimitedKey );
    }

    // 5: Build XML document to return to API client:
    userID = 0;
    apiFullKey = "";
    apiLimitedKey = "";
    apiRole = 0;
    //status = m_db.GetAccountIdFromUsername( username, &accountID );

    status = m_db.GetApiAccountInfoUsingAccountID( accountID, &userID, &apiFullKey, &apiLimitedKey, &apiRole );
    if( !status )
    {
        sLog.Error( "APIAccountManager::_APIKeyRequest()", "ERROR: username='******' cannot be found in 'account' table.", username.c_str() );
        return BuildErrorXMLResponse( "203", "Authentication failure." );
    }

    // 6: Return the userID and the API key requested in the xml structure:
    _BuildXMLHeader();
    {
        _BuildSingleXMLTag( "userID", std::string(itoa(userID)) );
        if( keyType == "full" )
        {
            keyTag = "apiFullKey";
            _BuildSingleXMLTag( keyTag, apiFullKey );
        }
        else
        {
            keyTag = "apiLimitedKey";
            _BuildSingleXMLTag( keyTag, apiLimitedKey );
        }
        _BuildSingleXMLTag( "expiresOn", Win32TimeToString(Win32TimeNow() + 180*Win32Time_Day) );
    }
    _CloseXMLHeader( EVEAPI::CacheStyles::Long );

    return _GetXMLDocumentString();
}
std::tr1::shared_ptr<std::string> APICharacterManager::_CharacterSheet(const APICommandCall * pAPICommandCall)
{
    size_t i;

    sLog.Error( "APICharacterManager::_CharacterSheet()", "TODO: Insert code to validate userID and apiKey" );

    sLog.Debug("APICharacterManager::_CharacterSheet()", "EVEmu API - Character Service Manager - CALL: CharacterSheet.xml.aspx");

    if( pAPICommandCall->find( "userid" ) == pAPICommandCall->end() )
    {
        sLog.Error( "APICharacterManager::_CharacterSheet()", "ERROR: No 'userID' parameter found in call argument list - exiting with error and sending back NOTHING" );
        return BuildErrorXMLResponse( "106", "Must provide userID parameter for authentication." );
    }

    if( pAPICommandCall->find( "apikey" ) == pAPICommandCall->end() )
    {
        sLog.Error( "APICharacterManager::_CharacterSheet()", "ERROR: No 'apiKey' parameter found in call argument list - exiting with error and sending back NOTHING" );
        return BuildErrorXMLResponse( "203", "Authentication failure." );
    }

    if( pAPICommandCall->find( "characterid" ) == pAPICommandCall->end() )
    {
        sLog.Error( "APICharacterManager::_CharacterSheet()", "ERROR: No 'characterID' parameter found in call argument list - exiting with error and sending back NOTHING" );
        return BuildErrorXMLResponse( "105", "Invalid characterID." );
    }

    // Make calls to the APICharacterDB class to grab all data for this call and populate the xml structure with that data
    uint32 characterID = atoi( pAPICommandCall->find( "characterid" )->second.c_str() );
    std::vector<std::string> skillTypeIDList;
    std::vector<std::string> skillPointsList;
    std::vector<std::string> skillLevelList;
    std::vector<std::string> skillPublishedList;
    m_charDB.GetCharacterSkillsTrained( characterID, skillTypeIDList, skillPointsList, skillLevelList, skillPublishedList );

    std::vector<std::string> charInfoList;
    m_charDB.GetCharacterInfo( characterID, charInfoList );

    std::map<std::string, std::string> charAttributes;
    m_charDB.GetCharacterAttributes( characterID, charAttributes );

    sLog.Error( "APICharacterManager::_CharacterSheet()", "INFO: Sections Currently hard-coded: attributeEnhancers, certificates, corporation roles" );

    std::vector<std::string> rowset;
    _BuildXMLHeader();
    {
        _BuildXMLTag( "result" );
        {
            _BuildSingleXMLTag( "characterID", std::string(itoa(characterID)) );
            _BuildSingleXMLTag( "name", charInfoList.at(12) );
            _BuildSingleXMLTag( "DoB", Win32TimeToString(strtoull(charInfoList.at(8).c_str(), NULL, 0) ));
            _BuildSingleXMLTag( "race", charInfoList.at(11) );
            _BuildSingleXMLTag( "bloodLine", charInfoList.at(10) );
            _BuildSingleXMLTag( "ancestry", charInfoList.at(9) );
            _BuildSingleXMLTag( "gender", ( (atoi(charInfoList.at(14).c_str()) > 0) ? "Male" : "Female") );
            _BuildSingleXMLTag( "corporationName", charInfoList.at(13) );
            _BuildSingleXMLTag( "corporationID", charInfoList.at(2) );
            _BuildSingleXMLTag( "allianceName", "none" );
            _BuildSingleXMLTag( "allianceID", "0" );
            _BuildSingleXMLTag( "cloneName", "Clone Grade Pi" );
            _BuildSingleXMLTag( "cloneSkillPoints", "54600000" );
            _BuildSingleXMLTag( "balance", charInfoList.at(0) );

            // TODO: code these for real, as what follows are hard coded examples to fill out the whole xml document:
            // Attribute Enhancers (implants)
            _BuildXMLTag( "attributeEnhancers" );
            {
                /*
                _BuildXMLTag( "memoryBonus" );
                {
                    _BuildSingleXMLTag( "augmentatorName", "Memory Augmentation - Basic" );
                    _BuildSingleXMLTag( "augmentatorValue", "3" );
                }
                _CloseXMLTag(); // close tag "memoryBonus"
                _BuildXMLTag( "perceptionBonus" );
                {
                    _BuildSingleXMLTag( "augmentatorName", "Ocular Filter - Basic" );
                    _BuildSingleXMLTag( "augmentatorValue", "3" );
                }
                _CloseXMLTag(); // close tag "perceptionBonus"
                _BuildXMLTag( "willpowerBonus" );
                {
                    _BuildSingleXMLTag( "augmentatorName", "Neural Boost - Basic" );
                    _BuildSingleXMLTag( "augmentatorValue", "3" );
                }
                _CloseXMLTag(); // close tag "willpowerBonus"
                _BuildXMLTag( "intelligenceBonus" );
                {
                    _BuildSingleXMLTag( "augmentatorName", "Snake Delta" );
                    _BuildSingleXMLTag( "augmentatorValue", "3" );
                }
                _CloseXMLTag(); // close tag "intelligenceBonus"
                _BuildXMLTag( "charismaBonus" );
                {
                    _BuildSingleXMLTag( "augmentatorName", "Limited Social Adaptation Chip" );
                    _BuildSingleXMLTag( "augmentatorValue", "3" );
                }
                _CloseXMLTag(); // close tag "charismaBonus"
                */
            }
            _CloseXMLTag(); // close tag "attributeEnhancers"

            // TODO: code these for real, as what follows are hard coded examples to fill out the whole xml document:
            // Attributes
            _BuildXMLTag( "attributes" );
            {
                _BuildSingleXMLTag( "intelligence", charAttributes.find( std::string(itoa(AttrIntelligence)))->second );
                _BuildSingleXMLTag( "memory", charAttributes.find( std::string(itoa(AttrMemory)))->second );
                _BuildSingleXMLTag( "charisma", charAttributes.find( std::string(itoa(AttrCharisma)))->second );
                _BuildSingleXMLTag( "perception", charAttributes.find( std::string(itoa(AttrPerception)))->second );
                _BuildSingleXMLTag( "willpower", charAttributes.find( std::string(itoa(AttrWillpower)))->second );
            }
            _CloseXMLTag(); // close tag "attributes"

            // Skills
            rowset.clear();
            rowset.push_back("typeID");
            rowset.push_back("skillpoints");
            rowset.push_back("level");
            rowset.push_back("published");
            _BuildXMLRowSet( "skills", "typeID", &rowset );
            {
                for(i=0; i<skillTypeIDList.size(); i++)
                {
                    rowset.clear();
                    rowset.push_back(skillTypeIDList.at(i));
                    rowset.push_back(skillPointsList.at(i));
                    rowset.push_back(skillLevelList.at(i));
                    rowset.push_back(skillPublishedList.at(i));
                    _BuildXMLRow( &rowset );
                }
            }
            _CloseXMLRowSet();  // close rowset "skills"

            // TODO: code these for real, as what follows are hard coded examples to fill out the whole xml document:
            // Certificates:
            rowset.clear();
            rowset.push_back("certificateID");
            _BuildXMLRowSet( "certificates", "certificateID", &rowset );
            {
            //    for(i=0; i<skillTypeIDList.size(); i++)
            //    {
                    rowset.clear();
                    //rowset.push_back(certificatesList.at(i));
                    rowset.push_back("1");
                    _BuildXMLRow( &rowset );
            //    }
            }
            _CloseXMLRowSet();  // close rowset "certificates"

            // TODO: code these for real, as what follows are hard coded examples to fill out the whole xml document:
            // Corporation Roles:
            //rowset.clear();
            //rowset.push_back("");
            //_BuildXMLRowSet( "", "", &rowset );
            //{
            //}
            _CloseXMLRowSet();  // close rowset ""
        }
        _CloseXMLTag(); // close tag "result"
    }
    _CloseXMLHeader( EVEAPI::CacheStyles::Long );

    return _GetXMLDocumentString();
}
std::tr1::shared_ptr<std::string> APICharacterManager::_SkillInTraining(const APICommandCall * pAPICommandCall)
{
    sLog.Error( "APICharacterManager::_SkillInTraining()", "TODO: Insert code to validate userID and apiKey" );

    sLog.Debug("APICharacterManager::_SkillInTraining()", "EVEmu API - Character Service Manager - CALL: SkillInTraining.xml.aspx");

    if( pAPICommandCall->find( "userid" ) == pAPICommandCall->end() )
    {
        sLog.Error( "APICharacterManager::_SkillInTraining()", "ERROR: No 'userID' parameter found in call argument list - exiting with error and sending back NOTHING" );
        return BuildErrorXMLResponse( "106", "Must provide userID parameter for authentication." );
    }

    if( pAPICommandCall->find( "apikey" ) == pAPICommandCall->end() )
    {
        sLog.Error( "APICharacterManager::_SkillInTraining()", "ERROR: No 'apiKey' parameter found in call argument list - exiting with error and sending back NOTHING" );
        return BuildErrorXMLResponse( "203", "Authentication failure." );
    }

    if( pAPICommandCall->find( "characterid" ) == pAPICommandCall->end() )
    {
        sLog.Error( "APICharacterManager::_SkillInTraining()", "ERROR: No 'characterID' parameter found in call argument list - exiting with error and sending back NOTHING" );
        return BuildErrorXMLResponse( "105", "Invalid characterID." );
    }

    // Make calls to the APICharacterDB class to grab all data for this call and populate the xml structure with that data
    uint32 characterID = atoi( pAPICommandCall->find( "characterid" )->second.c_str() );

    std::map<std::string, std::string> charLearningAttributesString;
    std::map<uint32, uint32> charLearningAttributes;
    m_charDB.GetCharacterAttributes( characterID, charLearningAttributesString );
    charLearningAttributes.insert( std::pair<uint32, uint32>( AttrMemory, ((uint32)(atoi(charLearningAttributesString.find(std::string(itoa(AttrMemory)))->second.c_str()))) ));
    charLearningAttributes.insert( std::pair<uint32, uint32>( AttrIntelligence, ((uint32)(atoi(charLearningAttributesString.find(std::string(itoa(AttrIntelligence)))->second.c_str()))) ));
    charLearningAttributes.insert( std::pair<uint32, uint32>( AttrCharisma, ((uint32)(atoi(charLearningAttributesString.find(std::string(itoa(AttrCharisma)))->second.c_str()))) ));
    charLearningAttributes.insert( std::pair<uint32, uint32>( AttrWillpower, ((uint32)(atoi(charLearningAttributesString.find(std::string(itoa(AttrWillpower)))->second.c_str()))) ));
    charLearningAttributes.insert( std::pair<uint32, uint32>( AttrPerception, ((uint32)(atoi(charLearningAttributesString.find(std::string(itoa(AttrPerception)))->second.c_str()))) ));

    std::vector<std::string> queueOrderList;
    std::vector<std::string> queueSkillTypeIdList;
    std::vector<std::string> queueSkillLevelList;
    std::vector<std::string> queueSkillRankList;
    std::vector<std::string> queueSkillIdList;
    std::vector<std::string> queueSkillPrimaryAttrList;
    std::vector<std::string> queueSkillSecondaryAttrList;
    std::vector<std::string> queueSkillPointsTrainedList;

    uint32 queueSkillPrimaryAttribute;
    uint32 queueSkillSecondaryAttribute;
    std::vector<uint32> queueSkillStartSP;
    std::vector<uint32> queueSkillEndSP;
    std::vector<uint64> queueSkillStartTime;
    std::vector<uint64> queueSkillEndTime;
    EvilNumber spPerMinute(0.0);
    EvilNumber skillStartSP(0.0);
    EvilNumber skillEndSP(0.0);
    EvilNumber timeNow(0.0);
    uint64 skillStartTime;
    uint64 skillEndTime;

    bool status = m_charDB.GetCharacterSkillQueue( characterID, queueOrderList, queueSkillTypeIdList, queueSkillLevelList, queueSkillRankList,
        queueSkillIdList, queueSkillPrimaryAttrList, queueSkillSecondaryAttrList, queueSkillPointsTrainedList );

    if( status )
    {
        sLog.Error( "APICharacterManager::_SkillInTraining()", "INFO: Calculation of Skill End Time based on Effective SP/min does NOT include implants/boosters at this time" );
        timeNow = EvilTimeNow();

        queueSkillPrimaryAttribute = charLearningAttributes.find( atoi(queueSkillPrimaryAttrList.at(0).c_str()) )->second;
        queueSkillSecondaryAttribute = charLearningAttributes.find( atoi(queueSkillSecondaryAttrList.at(0).c_str()) )->second;
        skillStartSP = EvilNumber(atoi( queueSkillPointsTrainedList.at(0).c_str() ));
        queueSkillStartSP.push_back( static_cast<uint32>( skillStartSP.get_int() ));
        skillEndSP = SkillPointsAtLevel( atoi(queueSkillLevelList.at(0).c_str()), atoi(queueSkillRankList.at(0).c_str()) );
        queueSkillEndSP.push_back( static_cast<uint32>( skillEndSP.get_int() ) );
        spPerMinute = SkillPointsPerMinute( queueSkillPrimaryAttribute, queueSkillSecondaryAttribute );
        skillStartTime = static_cast<uint64>(SkillStartingTime( skillStartSP, skillEndSP, spPerMinute, timeNow ).get_int());
        skillEndTime = static_cast<uint64>(SkillEndingTime( skillStartSP, skillEndSP, spPerMinute, timeNow ).get_int());
        queueSkillStartTime.push_back( skillStartTime );
        queueSkillEndTime.push_back( skillEndTime );
    }

    // EXAMPLE:
    std::vector<std::string> rowset;
    _BuildXMLHeader();
    {
        _BuildXMLTag( "result" );
        {
            if( status )
            {
                _BuildSingleXMLTag( "currentTQTime", Win32TimeToString(static_cast<uint64>(timeNow.get_int())) );
                _BuildSingleXMLTag( "trainingEndTime", Win32TimeToString(skillEndTime) );
                _BuildSingleXMLTag( "trainingStartTime", Win32TimeToString(skillStartTime) );
                _BuildSingleXMLTag( "trainingTypeID", queueSkillTypeIdList.at(0) );
                _BuildSingleXMLTag( "trainingStartSP", std::string(itoa(skillStartSP.get_int())) );
                _BuildSingleXMLTag( "trainingDestinationSP", std::string(itoa(skillEndSP.get_int())) );
                _BuildSingleXMLTag( "trainingToLevel", queueSkillLevelList.at(0) );
                _BuildSingleXMLTag( "skillInTraining", "1" );
            }
            else
            {
                _BuildSingleXMLTag( "skillInTraining", "0" );
            }
        }
        _CloseXMLTag(); // close tag "result"
    }
    _CloseXMLHeader( EVEAPI::CacheStyles::Modified );

    return _GetXMLDocumentString();
}