EvilNumber Character::GetSPPerMin( SkillRef skill )
{
    EvilNumber primarySkillTrainingAttr = skill->GetAttribute(AttrPrimaryAttribute);
    EvilNumber secondarySkillTrainingAttr = skill->GetAttribute(AttrSecondaryAttribute);

    EvilNumber primarySPperMin = GetAttribute( (uint32)(primarySkillTrainingAttr.get_int()) );
    EvilNumber secondarySPperMin = GetAttribute( (uint32)(secondarySkillTrainingAttr.get_int()) );

    //EvilNumber skillLearningLevel(0);
    //
    ////3374 - Skill Learning
    //SkillRef skillLearning = GetSkill( 3374 );
    //if( skillLearning )
    //    skillLearningLevel = skillLearning->GetAttribute(AttrSkillLevel);

    primarySPperMin = primarySPperMin + secondarySPperMin / 2.0f;
    //primarySPperMin = primarySPperMin * (EvilNumber(1.0f) + EvilNumber(0.02f) * skillLearningLevel);

    // 100% Training bonus for 30day and under character age has been removed in Incursion
    // http://www.eveonline.com/en/incursion/article/57/learning-skills-are-going-away
    // Check Total SP Trained for this character against the threshold for granting the 100% training bonus:
    //if( m_totalSPtrained.get_float() < ((double)MAX_SP_FOR_100PCT_TRAINING_BONUS) )
    //    primarySPperMin = primarySPperMin * EvilNumber(2.0f);

    return primarySPperMin;
}
EvilNumber Character::GetSPPerMin( SkillRef skill )
{
    EvilNumber primarySkillTrainingAttr = skill->GetAttribute(AttrPrimaryAttribute);
    EvilNumber secondarySkillTrainingAttr = skill->GetAttribute(AttrSecondaryAttribute);

    EvilNumber primarySPperMin = GetAttribute( (uint32)(primarySkillTrainingAttr.get_int()) );
    EvilNumber secondarySPperMin = GetAttribute( (uint32)(secondarySkillTrainingAttr.get_int()) );

    EvilNumber skillLearningLevel(0);

    //3374 - Skill Learning
    SkillRef skillLearning = GetSkill( 3374 );
    if( skillLearning )
        skillLearningLevel = skillLearning->GetAttribute(AttrSkillLevel);

    primarySPperMin = primarySPperMin + secondarySPperMin / 2.0f;
    primarySPperMin = primarySPperMin * (EvilNumber(1.0f) + EvilNumber(0.02f) * skillLearningLevel);

    // Check Total SP Trained for this character against the threshold for granting the 100% training bonus:
    if( m_totalSPtrained.get_float() < ((double)MAX_SP_FOR_100PCT_TRAINING_BONUS) )
        primarySPperMin = primarySPperMin * EvilNumber(2.0f);

    return primarySPperMin;
}
void Character::_CalculateTotalSPTrained()
{
    // Loop through all skills trained and calculate total SP this character has trained so far,
    // NOT including the skill currently being trained:
    double exponent = 0.0f;
    double totalSP = 0.0f;
    EvilNumber skillLevel;
    EvilNumber skillRank;
    std::vector<InventoryItemRef> skills;
    GetSkillsList( skills );
    std::vector<InventoryItemRef>::iterator cur, end;
    cur = skills.begin();
    end = skills.end();
    for(; cur != end; cur++)
    {
        // Calculate exact SP from each skill and add to total SP
        skillLevel = cur->get()->GetAttribute( AttrSkillLevel );
        skillRank = cur->get()->GetAttribute( AttrSkillTimeConstant );
        totalSP += 250.0f * (double)(skillRank.get_int()) * pow(32.0, (double)(((double)(skillLevel.get_int()) - 1.0f) / 2.0f));
    }

    m_totalSPtrained = totalSP;
}
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();
}
void ModuleManager::LoadCharge(InventoryItemRef chargeRef, EVEItemFlags flag)
{
    ActiveModule * mod = (ActiveModule *)(m_Modules->GetModule(flag));			// Should not be dangrous to assume ALL modules where charges are loaded are ACTIVE modules
    if( mod != NULL )
    {
		// Scenarios to handle:
		// + no charge loaded: check capacity >= volume of charge to add, if true, LOAD
		//     - ELSE: if charge to load is qty > 1, calculate smallest integer qty that will EQUAL capacity, SPLIT remainder off, then LOAD!
		// + some charge loaded: check capacity >= volume of charge to add, if true, MERGE new charge to existing
		//     - ELSE: if charge to load is qty > 1, calculate smallest integer qty that added to existing charge qty will EQUAL capacity, SPLIT remainder off, then LOAD!

		// Key facts to get:
		// * existing charge ref -> qty and volume/unit
		// * module ref -> capacity of module
		// * charge to add ref -> qty and volume/unit

		EvilNumber modCapacity = mod->getItem()->GetAttribute(AttrCapacity);
		EvilNumber chargeToLoadVolume = chargeRef->GetAttribute(AttrVolume);
		EvilNumber chargeToLoadQty = EvilNumber(chargeRef->quantity());

		/////////////////////////////////////////
		// chargeRef->Split();
		// chargeRef->Merge();
		// mod->Load(chargeRef);
		// chargeRef->Move(m_Ship->itemID(), flag);		// used to be (m_pOperator->GetLocationID(), flag)
		/////////////////////////////////////////

		//m_Ship->GetOperator()->Client()->MoveItem(chargeRef->itemID(), m_Ship->itemID(), flag);

		if( mod->isLoaded() )
		{
			// Module is loaded, let's check available capacity:
			InventoryItemRef loadedChargeRef = mod->getLoadedChargeRef();
			EvilNumber loadedChargeVolume = loadedChargeRef->GetAttribute(AttrVolume);
			EvilNumber loadedChargeQty = EvilNumber(loadedChargeRef->quantity());
			modCapacity -= (loadedChargeVolume * loadedChargeQty);		// Calculate remaining capacity
			if( chargeRef->typeID() != loadedChargeRef->typeID() )
			{
				// Different charge type is being swapped into this module, so unload what's loaded
				if( IsStation(m_Ship->GetOperator()->GetLocationID()) )
					loadedChargeRef->Move(m_Ship->locationID(), flagHangar);
				else
				{
					m_Ship->ValidateAddItem(flagCargoHold,loadedChargeRef);
					loadedChargeRef->Move(m_Ship->itemID(), flagCargoHold);
				}
				mod->unload();

				// Loading of charge will be performed below
			}
			else
			{
				if( modCapacity > chargeToLoadVolume )
				{
					// Great!  We can load at least one, let's top off the loaded charges:
					uint32 quantityWeCanLoad = floor((modCapacity / chargeToLoadVolume).get_float());
					if( quantityWeCanLoad > 0 )
					{
						if( quantityWeCanLoad < chargeToLoadQty.get_int() )
						{
							// Split chargeRef to qty 'quantityWeCanLoad'
							// Merge new smaller qty 'quantityWeCanLoad' with loadedChargeRef
							// Load this merged charge Ref into module
							InventoryItemRef loadableChargeQtyRef = chargeRef->Split( quantityWeCanLoad );
							loadableChargeQtyRef->ChangeOwner( chargeRef->ownerID() );
							loadedChargeRef->Merge( loadableChargeQtyRef );
							mod->load( loadedChargeRef );
							loadedChargeRef->Move(m_Ship->itemID(), flag);		// used to be (m_pOperator->GetLocationID(), flag)
						}
						else
						{
							// Merge chargeRef with loadedChargeRef
							// Load this merged charge Ref into module
							loadedChargeRef->Merge( chargeRef );
							mod->load( loadedChargeRef );
							loadedChargeRef->Move(m_Ship->itemID(), flag);		// used to be (m_pOperator->GetLocationID(), flag)
						}
					}
					else
						throw PyException( MakeCustomError( "Cannot load even one unit of this charge!" ) );
				}
				else
				{
					throw PyException( MakeCustomError( "Charge is full!" ) );
				}
			}
		}

		// Refresh ammo capacity of module in case it was modified in previous code block ahead of a load action:
		modCapacity = mod->getItem()->GetAttribute(AttrCapacity);

		// Load charge supplied if this module was either never loaded, or just unloaded from a different type right above:
		if( !(mod->isLoaded()) )
		{
			// Module is not loaded at all, let's check total volume of charge to load against available capacity:
			if( modCapacity >= (chargeToLoadVolume * chargeToLoadQty) )
			{
				// We can insert entire stack of chargeRef into module
				// Load chargeRef as-is into module
				mod->load( chargeRef );
				chargeRef->Move(m_Ship->itemID(), flag);		// used to be (m_pOperator->GetLocationID(), flag)
			}
			else
			{
				// We need to split off only as many charge units as can fit into this module
				// Split chargeRef
				uint32 quantityWeCanLoad = floor((modCapacity / chargeToLoadVolume).get_float());
				if( quantityWeCanLoad > 0 )
				{
					// Split chargeRef to qty 'quantityWeCanLoad'
					// Merge new smaller qty 'quantityWeCanLoad' with loadedChargeRef
					// Load this merged charge Ref into module
					InventoryItemRef loadableChargeQtyRef = chargeRef->Split( quantityWeCanLoad );
					loadableChargeQtyRef->ChangeOwner( chargeRef->ownerID() );
					mod->load( loadableChargeQtyRef );
					loadableChargeQtyRef->Move(m_Ship->itemID(), flag);		// used to be (m_pOperator->GetLocationID(), flag)
				}
				else
		            throw PyException( MakeCustomError( "Cannot load even one unit of this charge!" ) );
			}
		}
    }
}
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) );
                currentTraining->SetAttribute(AttrSkillPoints, nextLevelSP - (minRemaining * SPPerMinute));
            }

            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;
            }

            _log( ITEM__TRACE, "%s (%u): Starting training of skill %s (%u).", m_itemName.c_str(), m_itemID, currentTraining->itemName().c_str(), currentTraining->itemID() );

            EvilNumber SPPerMinute = GetSPPerMin( currentTraining );
            //  double SPToNextLevel = currentTraining->GetSPForLevel( currentTraining->skillLevel() + 1 ) - currentTraining->skillPoints();
            EvilNumber SPToNextLevel = currentTraining->GetSPForLevel( currentTraining->GetAttribute(AttrSkillLevel) + 1) - currentTraining->GetAttribute(AttrSkillPoints);

            //uint64 timeTraining = nextStartTime + Win32Time_Minute * SPToNextLevel / SPPerMinute;
            EvilNumber timeTraining = nextStartTime + EvilTime_Minute * SPToNextLevel / SPPerMinute;

            currentTraining->MoveInto( *this, flagSkillInTraining );
            //currentTraining->Set_expiryTime( timeTraining );
            currentTraining->SetAttribute(AttrExpiryTime, timeTraining);

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

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

                c->UpdateSkillTraining();
            }
        }

        //if( currentTraining->expiryTime() <= Win32TimeNow() )
        if( currentTraining->GetAttribute(AttrExpiryTime) <= EvilTimeNow() ) {
            // training has been finished:
            _log( ITEM__ERROR, "%s (%u): Finishing training of skill %s (%u).", itemName().c_str(), itemID(), currentTraining->itemName().c_str(), currentTraining->itemID() );

            //currentTraining->Set_skillLevel( currentTraining->skillLevel() + 1 );
            //currentTraining->Set_skillPoints( currentTraining->GetSPForLevel( currentTraining->skillLevel() ) );

            //nextStartTime = currentTraining->expiryTime();
            //currentTraining->Clear_expiryTime();

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

            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();
}