// Ryzom - MMORPG Framework
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
#include "stdpch.h"
#include "harvest_source.h"
#include "deposit.h"
#include "egs_globals.h"
#include "phrase_manager/phrase_utilities_functions.h"
#include "entity_manager/entity_base.h"
#include "player_manager/player_manager.h"
#include "player_manager/player.h"
#include "player_manager/character.h"
#include "forage_progress.h"
#include "range_selector.h"
#include "egs_sheets/egs_sheets.h"
#include "nel/misc/variable.h"
#include "phrase_manager/s_effect.h"
#include "server_share/r2_vision.h"
using namespace NLMISC;
using namespace NLNET;
using namespace std;
NL_INSTANCE_COUNTER_IMPL(CHarvestSource);
NL_INSTANCE_COUNTER_IMPL(CHarvestSourceManager);
NL_ISO_TEMPLATE_SPEC CSimpleEntityManager *CSimpleEntityManager::_Instance = NULL;
uint NbAutoSpawnedForageSources = 0;
const NLMISC::TGameCycle MaxT = 1200; // 2 min for prospected sources (TODO: external data)
const float CommonRiskThreshold = 127.0f;
const float ThresholdD1 = CommonRiskThreshold;
const float ThresholdD2 = CommonRiskThreshold * 1.20f;
const float ThresholdE = CommonRiskThreshold;
const float ThresholdC = CommonRiskThreshold;
const float DeltaMoveBarPerTick = (DeltaMoveBarPerSec / 10.0f);
const float DeltaResetBarPerTick = (DeltaResetBarPerSec / 10.0f);
sint32 ForageSourceDisplay = -1;
static const NLMISC::TGameCycle Locked = ~0;
static const NLMISC::TGameCycle IniTime = (~0)-1; // highest positive integer (suitable for unsigned)
// Minimum extraction session:
// 6 extractions = 24 seconds with 4 seconds per extraction
// Maximum extraction session:
// 80 extractions = 120 seconds with 1.5 second per extraction
// MaxA: 3.5
// MaxS: 1/15
// nlctassert(MaxAS>0);
const float MaxAS = (3.5f / 15.0f);
// MaxRequiredQ: 250; InitResultQ: 1 => MaxDeltaQ: 26
// nlctassert(MaxDeltaQ>0);
const float MaxQ = 250.0f;
const float MaxDeltaQ = 26.0f;
// { D, E, C } impacted by (0=Qtty, 1=Qlty, 2=Both)
uint ImpactSchemes [6][3] = { { 0, 1, 2 }, { 0, 2, 1 }, { 1, 0, 2 }, { 1, 2, 0 }, { 2, 0, 1 }, { 2, 1, 0 } };
// Observed impact on D (/10): 1 3 6 10 3 1
// Observed impact on E (/10): 6 3 1 1 3 10
// Note: if modifying this schemes, please change FORAGE_SOURCE_IMPACT_MODE in phrase_en.txt.
uint SpecialNewbieImpactSchemeD = 10;
uint16 LowDangerMappings [2] = { SpecialNewbieImpactSchemeD+1, SpecialNewbieImpactSchemeD+4 };
sint8 ExplosionResetPeriod = 50; // 5 s
CHarvestSource AutoSpawnSourceIniProperties;
/*
* Access to singleton
*/
CHarvestSourceManager *CHarvestSourceManager::getInstance()
{
return (CHarvestSourceManager*)_Instance;
}
/*
* Initialization of source manager
*/
void CHarvestSourceManager::init( TDataSetIndex baseRowIndex, TDataSetIndex size )
{
CSimpleEntityManager::init( baseRowIndex, size );
// Note: Now, most of these values are overridden by deposit settings (see CFgProspectionPhrase::autoSpawnSource())
AutoSpawnSourceIniProperties.setLifetime( 6000 ); // 10 min
AutoSpawnSourceIniProperties.setProspectionExtraExtractionTime( 0 ); // no extra time
//AutoSpawnSourceIniProperties.setDistVis( 100 );
}
void CHarvestSourceManager::release()
{
delete (CHarvestSourceManager*)_Instance;
}
/*
* HarvestSource constructor
*/
CHarvestSource::CHarvestSource()
{
_NbExtractions = 0;
_ForageSite = NULL;
_DepositForK = NULL;
// Set default values (TODO: external data)
_LifeTime = 200; // 20 s
_ExtractionTime = 250; // 25 s for initial extraction time (now, recalculated using quality of material)
_ExtraExtractionTime = 25; // 2.5 s
_IncludedBonusExtractionTime = 0;
_T = Locked; // until spawn is completed
_S = 0.025f; // 4 s per delivery (initial value may be not used by the extraction action)
_A = 0.1f;
_Q = 1.0f;
_D = 0;
_E = 0;
_TargetRTProps[TargetD] = 0;
_TargetRTProps[TargetE] = 0;
//_C = 0;
_MaxQuality = ~0;
_IImpactMappingScheme = 0;
_N = 0;
_IsInNewbieMode = false;
_BonusForAPct = 0;
//_DistanceVisibility = 80;
//_StealthVisibility = VISIBILITY_RIGHTS::All;
_IsExtractionInProgress = false;
_ExplosionResetCounter = -1;
_IsAutoSpawned = false;
_SafeSource = false;
_NbEventTriggered = 0;
}
/*
* HarvestSource destructor
*/
CHarvestSource::~CHarvestSource()
{
// unregister the deposit auto spawn if any
setDepositAutoSpawn(NULL);
}
/*
* setDepositAutoSpawn
*/
void CHarvestSource::setDepositAutoSpawn(CDeposit *deposit)
{
// unregister the current deposit, if any
if(_DepositAutoSpawn)
{
_DepositAutoSpawn->decreaseAutoSpawnedSources();
_DepositAutoSpawn= NULL;
}
// register the new one if any
if(deposit)
{
_DepositAutoSpawn= deposit;
_DepositAutoSpawn->increaseAutoSpawnedSources();
}
}
/*
* Init the source. All pointers must be valid (but forageSite may be NULL).
* Return false if the current quantity in the deposit is 0.
*/
bool CHarvestSource::init( const CHarvestSource& ini, const NLMISC::CVector2f& pos,
CRecentForageSite *forageSite,
CDeposit *depositForK,
const CStaticDepositRawMaterial *rmInfo, float quantityRatio )
{
initFrom( ini );
setPos( pos );
setForageSite( forageSite );
bool isNonEmpty = setRawMaterial( rmInfo, quantityRatio );
// Set link to deposit for kami anger level
_DepositForK = depositForK;
return isNonEmpty;
}
/*
* Set the raw material, the initial amount and the max quality, or return false if the current quantity in the deposit is 0.
*/
bool CHarvestSource::setRawMaterial( const CStaticDepositRawMaterial *rmInfo, float quantityRatio )
{
H_AUTO(CHarvestSource_setRawMaterial);
_MaterialSheet = rmInfo->MaterialSheet;
// Get corresponding initial quantity & max quality
const CStaticItem *staticItem = CSheets::getForm( _MaterialSheet );
if ( staticItem && staticItem->Mp )
{
/// The quantity is a fraction of the 'Stackable' property (but it is limited by the constraints of the deposit)
_N = min( max( (((float)staticItem->Stackable) * quantityRatio), 1.0f ), _ForageSite->getQuantityInDeposit() );
// Select either the MaxQuality in the deposit primitive or in the RM sheet (if -1 in the deposit, or no deposit)
sint16 depositMaxQuality = _ForageSite ? _ForageSite->deposit()->maxQuality() : -1;
if ( depositMaxQuality != -1 )
_MaxQuality = (uint16)depositMaxQuality;
else
_MaxQuality = staticItem->Mp->MaxQuality;
//nldebug( "Quality limited by source to %hu", _MaxQuality );
if ( _N == 0 )
return false;
}
else
{
nlwarning( "%s is not a valid raw material sheet", _MaterialSheet.toString().c_str() );
return false;
}
return true;
}
/*
* Recalculate the remaining extraction time depending on the requested quality.
* Does nothing if the source is in "extra extraction time".
* _ExtraExtractionTime is included in _ExtraExtractionTime.
*
* Precondition: _ExtractionTime != 0
*/
void CHarvestSource::recalcExtractionTime( float requestedQuality )
{
if ( _T > _ExtraExtractionTime )
{
nlassert( _ExtractionTime > _ExtraExtractionTime );
float timeRatio = ((float)_T) / ((float)_ExtractionTime);
//float fExtractionTime = requestedQuality*(0.3092f*10.0f) + (22.0f*10.0f); // Q10 -> 25 s; Q250 -> 1'40 s
float fExtractionTime = requestedQuality*ForageExtractionTimeSlopeGC.get() + ForageExtractionTimeMinGC.get() + (float)_ExtraExtractionTime;
_ExtractionTime = (NLMISC::TGameCycle)fExtractionTime;
_T = (NLMISC::TGameCycle)(fExtractionTime * timeRatio);
}
}
/*
* Prepare the source as an unpublished entity in mirror. Return false in case of failure.
* Must be called *after* init().
* prospectorDataSetRow must be either null (isNull()) or an accessible row (isAccessible()).
*/
bool CHarvestSource::spawnBegin( uint8 knowledgePrecision, const TDataSetRow& prospectorDataSetRow, bool isAutoSpawned )
{
H_AUTO(CHarvestSource_spawn);
// Add into mirror (but unpublished)
CEntityId entityId = CEntityId::getNewEntityId( RYZOMID::forageSource );
if ( ! Mirror.createEntity( entityId ) )
return false;
_DataSetRow = TheDataset.getDataSetRow( entityId );
_IsAutoSpawned = isAutoSpawned;
// Set the sheet id (including knowledge information)
uint sourceFXIndex = _ForageSite ? (uint)_ForageSite->deposit()->sourceFXIndex() : 0;
CMirrorPropValue sheet( TheDataset, _DataSetRow, DSPropertySHEET );
sheet = CSheetId( toString( "%u_%u.forage_source", sourceFXIndex, knowledgePrecision ) ).asInt();
// For knowledge 3, fit the sheet id of the RM sitem into NAME_STRING_ID (instead of a string id) !
CMirrorPropValue nameId( TheDataset, _DataSetRow, DSPropertyNAME_STRING_ID );
nameId = (knowledgePrecision == 3) ? _MaterialSheet.asInt() : 0;
if ( knowledgePrecision != 0 )
{
const CStaticItem *staticItem = CSheets::getForm( materialSheet() );
if ( staticItem && staticItem->Mp )
{
// Set additional knowledge info into VISUAL_FX
CMirrorPropValue visualFx( TheDataset, _DataSetRow, DSPropertyVISUAL_FX );
switch ( knowledgePrecision )
{
case 1: visualFx = (TYPE_VISUAL_FX)staticItem->Mp->getGroup(); break; // initializes the prop, but only 10 bits allowed
case 2: // family sent for 2 & 3 (for knowledge 3, used only as icon index)
case 3: visualFx = (TYPE_VISUAL_FX)staticItem->Mp->Family; break; // initializes the prop, but only 10 bits allowed
default:; // default value is 0
}
if ( (visualFx() & 0x400) != 0 )
{
nlwarning( "FG: Family or group index exceeding max!" ); // bit 10 is reserved for explosion FX
visualFx = 0;
}
}
}
// Set the target (the prospector, or a nul datasetrow if auto-spawned)
CMirrorPropValue targetRow( TheDataset, _DataSetRow, DSPropertyTARGET_ID );
targetRow = prospectorDataSetRow;
// Add to manager so that update() will be called at each game cycle
CHarvestSourceManager::getInstance()->addEntity( this ); // we don't do it in spawnEnd(), because the source would remain unpublished forever if the AIS quit before replying to the transport class message
return true;
}
/*
* Complete the source spawn: publish the entity in mirror or delete it.
* Caution: if authorized, the source object is deleted!
*/
void CHarvestSource::spawnEnd( bool authorized )
{
if ( ! authorized )
{
despawn();
CHarvestSourceManager::getInstance()->destroyEntity( _DataSetRow );
return;
}
// Unlock update
_ExtractionTime = IniTime; // will trigger calculation in recalcExtractionTime() because _T > _ExtraExtractionTime
_T = _ExtractionTime; // ratio 1.0 => beginning of extraction time
if ( _IsAutoSpawned )
++NbAutoSpawnedForageSources;
// Set the initial position
CMirrorPropValue posX( TheDataset, _DataSetRow, DSPropertyPOSX );
CMirrorPropValue posY( TheDataset, _DataSetRow, DSPropertyPOSY );
posX = (TYPE_POSX)(_Pos.x * 1000.0f);
posY = (TYPE_POSY)(_Pos.y * 1000.0f);
// Set the WhoSeesMe bitfield (distance of visibility by players (0-31) and creatures (32-63)) (now constant)
uint32 nbBitsOn = /*_DistanceVisibility*/80 * 32 / 250; // (250 m is the max, corresponding to 32 bits on
const uint64 distanceBitfield = IsRingShard? R2_VISION::buildWhoSeesMe(R2_VISION::WHOSEESME_VISIBLE_MOB,false): ((uint64)1 << (uint64)nbBitsOn) - 1; // (hide to creatures)
CMirrorPropValue whoSeesMe(TheDataset, _DataSetRow, DSPropertyWHO_SEES_ME );
whoSeesMe = distanceBitfield;
// Set selectable property to true
CMirrorPropValue contextualProperties(TheDataset, _DataSetRow, DSPropertyCONTEXTUAL );
CProperties prop(0);
prop.selectable(true);
contextualProperties = prop;
// Update bars and other variable properties
updateVisuals();
// Publish in mirror
TheDataset.declareEntity( _DataSetRow );
}
/*
* Return the prospector or a null datasetrow if there was no prospection (auto-spawn)
* The accessibility of this datasetrow must be checked before use.
*/
const TDataSetRow& CHarvestSource::getProspectorDataSetRow() const
{
CMirrorPropValueRO targetRow( TheDataset, _DataSetRow, DSPropertyTARGET_ID );
return targetRow();
}
/*
* Despawn the source in mirror, and exit from forage site
*/
void CHarvestSource::despawn()
{
H_AUTO(CHarvestSource_despawn);
// Remove from mirror
CEntityId entityId = TheDataset.getEntityId( _DataSetRow );
Mirror.removeEntity( entityId );
//nldebug( "--- %p from %p", this, _ForageSite );
// Exit from forage site (the entering is done outside class, because a failure may prevent to create the source)
if ( _ForageSite )
_ForageSite->removeActiveSource();
if ( _IsAutoSpawned )
--NbAutoSpawnedForageSources;
// End forage sessions and calculate XP and give result
if ( ! _Foragers.empty() )
{
// The first forager is the only one who can extract. Give result & dispatch XP
CForagers::const_iterator it = _Foragers.begin();
CCharacter *player = PlayerManager.getChar( *it );
if ( player && player->forageProgress() )
{
if ( player->forageProgress()->sourceRowId() == _DataSetRow ) // check if he has not changed his target
{
player->giveForageSessionResult( this );
}
}
// End sessions of care takers (all excluding the first element)
for ( ++it; it!=_Foragers.end(); ++it )
{
player = PlayerManager.getChar( *it );
if ( player && player->forageProgress() )
{
if ( player->forageProgress()->sourceRowId() == _DataSetRow ) // check if he has not changed his target
{
player->endForageSession();
}
}
}
}
}
/*
* Helper for updateVisiblePostPos()
*/
inline void updateBarValueAtTick( float& destValue, float& currentValue )
{
float diff = destValue - currentValue;
if ( diff > 0 )
{
currentValue = std::min( destValue, currentValue + DeltaMoveBarPerTick );
}
else if ( diff < 0 )
{
float delta = (currentValue == 0) ? DeltaResetBarPerTick : DeltaMoveBarPerTick;
currentValue = std::max( destValue, currentValue - delta );
}
}
/*
* Update
*/
bool CHarvestSource::update()
{
H_AUTO(CHarvestSource_update);
if ( _IsExtractionInProgress )
{
// Make the bar transitions smooth (needs to be done on server to match timing on client)
updateBarValueAtTick( _TargetRTProps[TargetD], _D );
updateBarValueAtTick( _TargetRTProps[TargetE], _E );
// Test damaging event risk
if ( _E > ThresholdE )
{
makeDamagingEvent();
setEventTriggered();
impactRTProp( TargetE, 0 );
}
// Test spawn risk
/*if ( _C > ThresholdC )
{
//sendMessageToExtractors( "CREATURE_SPAWN" );
// TODO
//setEventTriggered();
_C = 0;
}*/
// Test depletion risk (if the bar transition is still in progress, wait)
if ( (_D > ThresholdD1) )
{
// if high risk value, deplete all the forage site (if exist and allow depletion)
if ( _ForageSite && _ForageSite->allowDepletionRisk() && _TargetRTProps[TargetD] > ThresholdD2 )
{
sendMessageToExtractors( "FORAGE_SOURCE_SITE_DEPLETED" );
_ForageSite->depleteAll();
}
// else will just kill the current source (send appropriate message)
else
sendMessageToExtractors( "FORAGE_SOURCE_DEPLETED" );
// in all case kill the current source, and add bad event
setEventTriggered();
despawn();
return false;
}
// Test remaining time
else if ( _T == 0 )
{
despawn();
return false;
}
else
{
--_T;
float naturalMoveThreshold = CommonRiskThreshold - 2.0f;
if ( _TargetRTProps[TargetD]+ForageExtractionNaturalDDeltaPerTick.get() < naturalMoveThreshold ) // don't auto-move if it makes it trigger the event
impactRTProp( TargetD, _TargetRTProps[TargetD] + ForageExtractionNaturalDDeltaPerTick.get() );
if (!_SafeSource)
{
if ( _TargetRTProps[TargetE]+ForageExtractionNaturalEDeltaPerTick.get() < naturalMoveThreshold )
impactRTProp( TargetE, _TargetRTProps[TargetE] + ForageExtractionNaturalEDeltaPerTick.get() );
}
if ( _DataSetRow.getIndex() == (TDataSetIndex)ForageSourceDisplay )
nldebug( "T: %u", _T );
updateVisuals();
return true;
}
}
else
{
// Remain locked if the source is not fully spanwed yet
if ( _T == Locked )
return true;
// Test end of lifetime
if ( _LifeTime == 0 )
{
despawn();
return false;
}
else
{
--_LifeTime;
if ( _DataSetRow.getIndex() == (TDataSetIndex)ForageSourceDisplay )
nldebug( "_LifeTime: %u", _LifeTime );
return true;
}
}
}
/*
* Update visual properties & related stuff
*/
void CHarvestSource::updateVisuals()
{
H_AUTO(CHarvestSource_updateVisuals);
// Set the bars on the entity
TYPE_BARS statusBar;
float d = (_TargetRTProps[TargetD] > ForageCareBeginZone.get()) ? _TargetRTProps[TargetD] : 0;
if ( d > CommonRiskThreshold )
d = CommonRiskThreshold;
float e = (_TargetRTProps[TargetE] > ForageCareBeginZone.get()) ? _TargetRTProps[TargetE] : 0;
if ( e > CommonRiskThreshold )
e = CommonRiskThreshold;
statusBar = (_T==IniTime) ? 127 : uint32((_T*127/_ExtractionTime/2)*2); // time progression (round off to 2 to reduce flooding by 30% (taking D & E into account))
if ( _N != 0 )
statusBar = statusBar | ((uint32(_N)+1) << 7); // round off to next integer (TODO: ratio qtty/initial_qtty)
statusBar = statusBar | (uint32((127.0f-d)/**127.0f*/) << 14);
if (!_SafeSource) // If source is safe let 0 (client will display it full and with a special color)
statusBar = statusBar | (uint32((127.0f-e)/**127.0f*/) << 21);
statusBar = statusBar | (((uint32)(_IsExtractionInProgress)) << 28);
//statusBar = statusBar | (uint32(_C/**127.0f*/) << 28);
CMirrorPropValue statusBarProp( TheDataset, _DataSetRow, DSPropertyBARS );
statusBarProp = statusBar;
/*if ( (_N < 0) || (_N > 127) )
nlwarning( "FG: N = %g", _N );
if ( (_D < 0) || (_D > 127) )
nlwarning( "FG: N = %g", _D );
if ( (_E < 0) || (_E > 127) )
nlwarning( "FG: N = %g", _E );*/
// Set kami angry level information & extra time state (store in orientation!)
float angryLevel7 = 127.0f - (127.0f * _DepositForK->kamiAnger() / ForageKamiAngerThreshold2.get());
uint extraTime7 = _ExtraExtractionTime * 127 / _ExtractionTime;
uint inclBonusExtraTime7 = _IncludedBonusExtractionTime * 127 / _ExtractionTime;
CMirrorPropValue angryLevelExtraTimeProp( TheDataset, _DataSetRow, DSPropertyORIENTATION );
TYPE_ORIENTATION angryLevelExtraTimePropV = angryLevel7;
if ( _IsExtractionInProgress )
angryLevelExtraTimePropV += (( ((float)inclBonusExtraTime7) * 128.0f + (float)extraTime7) * 128.0f);
angryLevelExtraTimeProp = angryLevelExtraTimePropV; // stored as float, should be converted to 21b uint by FS
// Reset explosion visual fx if needed
if ( _ExplosionResetCounter == 0 )
{
CMirrorPropValue visualFx( TheDataset, _DataSetRow, DSPropertyVISUAL_FX );
visualFx = (visualFx() & 0x3FF); // unset bit 10
_ExplosionResetCounter = -1;
}
else if ( _ExplosionResetCounter > 0 )
{
--_ExplosionResetCounter;
}
}
/*
*
*/
void CHarvestSource::sendMessageToExtractors( const char *msg )
{
H_AUTO(CHarvestSource_sendMessageToExtractors);
CForagers::const_iterator it;
for ( it=_Foragers.begin(); it!=_Foragers.end(); ++it )
{
const TDataSetRow& extRowId = (*it);
PHRASE_UTILITIES::sendDynamicSystemMessage( extRowId, msg );
}
}
/*
*
*/
void CHarvestSource::sendMessageToExtractors( const char *msg, sint32 param )
{
H_AUTO(CHarvestSource_sendMessageToExtractors);
CForagers::const_iterator it;
for ( it=_Foragers.begin(); it!=_Foragers.end(); ++it )
{
const TDataSetRow& extRowId = (*it);
SM_STATIC_PARAMS_1(params, STRING_MANAGER::integer);
params[0].Int = param;
PHRASE_UTILITIES::sendDynamicSystemMessage( extRowId, msg, params );
}
}
CVariable ForageForceImpactScheme( "egs", "ForageForceImpactScheme", "", -1 );
/*
* Begin an extraction action. Once the extraction process is started, the source remains in
* extraction mode until the extraction time is elapsed (even if players stop/restart
* extracting).
* Return true if the forager is the extractor (the first one on the source)
*/
bool CHarvestSource::beginExtraction( const TDataSetRow& forager, bool isNewbie )
{
H_AUTO(CHarvestSource_beginExtraction);
bool foragerIsFirstExtractor = false;
if ( ! _IsExtractionInProgress )
{
_IsExtractionInProgress = true;
foragerIsFirstExtractor = true;
_IsInNewbieMode = isNewbie; // only the first one sets the mode
// Tell him the max quality
SM_STATIC_PARAMS_1(params, STRING_MANAGER::integer);
params[0].Int = (sint32)_MaxQuality;
PHRASE_UTILITIES::sendDynamicSystemMessage( forager, "FORAGE_SOURCE_MAXLEVEL", params );
// Set the impact scheme
setNewImpactScheme(); // at this time, _Foragers is empty so nobody is told yet
}
// Add player to extractor to list (if new)
CForagers::iterator it = find( _Foragers.begin(), _Foragers.end(), forager );
if ( it == _Foragers.end() )
{
_Foragers.push_back( forager );
// Tell him the impact mode
SM_STATIC_PARAMS_1(params, STRING_MANAGER::integer);
params[0].Int = (sint32)_IImpactMappingScheme;
PHRASE_UTILITIES::sendDynamicSystemMessage( forager, "FORAGE_SOURCE_IMPACT_MODE", params );
}
else if ( it == _Foragers.begin() )
{
foragerIsFirstExtractor = true;
}
return foragerIsFirstExtractor;
}
/*
* Set a new mapping scheme of property impact
*/
void CHarvestSource::setNewImpactScheme()
{
H_AUTO(CHarvestSource_setNewImpactScheme);
// Set mapping scheme of property impact
if ( _IsInNewbieMode )
{
// Force "low dangers" for 1 newbie extractor
_IImpactMappingScheme = (uint16)LowDangerMappings[RandomGenerator.rand( 1 )];
}
else
{
// Normal dangers
if ( ForageForceImpactScheme.get() == -1 )
_IImpactMappingScheme = (uint16)RandomGenerator.rand( 5 );
else
_IImpactMappingScheme = (uint16)ForageForceImpactScheme.get();
}
sendMessageToExtractors( "FORAGE_SOURCE_IMPACT_MODE", (sint32)_IImpactMappingScheme );
#ifdef NL_DEBUG
nldebug( "FG: map scheme: %u", _IImpactMappingScheme );
#endif
}
bool ForceDropProp = false;
NLMISC_VARIABLE( bool, ForceDropProp, "" );
CVariable ForceAbsorption( "egs", "ForceAbsorption", "", 0 );
/*
* Update the source state with an extraction (see doc in .h).
*/
void CHarvestSource::extractMaterial( float *reqPosProps, float *absPosProps, float qualityCeilingFactor, float qualitySlowFactor, float *results, float successFactor, uint8 lifeAbsorberRatio, const TDataSetRow& extractingEntityRow, CHarvestSource::TRealTimeProp& propDrop )
{
H_AUTO(CHarvestSource_extractMaterial);
CCharacter* player = PlayerManager.getChar( extractingEntityRow );
++_NbExtractions; // it's 1 at the first call, so that the initial value is used
float nbe = (float)_NbExtractions;
if ( (successFactor < 0.1f) || ForceDropProp )
{
if ( _NbExtractions < 6 )
propDrop = Q; // don't drop A at the beginning (wait to reach 1 (cumulated) would be better)
else
propDrop = (TRealTimeProp)(RandomGenerator.rand(1)+1);
nldebug( "Prop drop %u", propDrop );
}
// Aperture: converges towards the requested value, except when a drop occurs (0 at this step)
// if ( reqPosProps[A] > 0 )
{
float obtainedQuantity = (propDrop == A) ? 0.0f : (results[A]*ForageQuantitySlowFactor.get() + reqPosProps[A]) / (ForageQuantitySlowFactor.get()+1);
// Apply possible aperture bonus
if ( _BonusForAPct != 0 )
{
obtainedQuantity += obtainedQuantity * (float)((uint)_BonusForAPct) * 0.01f;
}
// Extract material
if ( _ForageSite )
obtainedQuantity = _ForageSite->consume( obtainedQuantity ); // consume (and limit) in forage site & deposit
if ( obtainedQuantity > _N ) // should not occur because _N uses the deposit quantity
obtainedQuantity = _N;
_N -= obtainedQuantity;
_A = (_A*nbe + obtainedQuantity) / (nbe + 1.0f); // average per source
_DepositForK->incKamiAnger( obtainedQuantity, _Foragers );
if ( (obtainedQuantity == 0) && (results[A] != 0) && (! (propDrop == A)) )
PHRASE_UTILITIES::sendDynamicSystemMessage( _Foragers[0], "FORAGE_DEPOSIT_IS_EMPTY" );
results[A] = obtainedQuantity;
// add spire effect ( quantity )
if ( player )
{
const CSEffect* pEffect = player->lookForActiveEffect( EFFECT_FAMILIES::TotemHarvestQty );
if ( pEffect != NULL )
{
results[A] *= ( 1.0f + pEffect->getParamValue() / 100.0f );
}
}
// add item special effect
if ( player )
{
std::vector effects = player->lookForSpecialItemEffects(ITEM_SPECIAL_EFFECT::ISE_FORAGE_ADD_RM);
std::vector::const_iterator it, itEnd;
double addedQty = 0.;
for (it=effects.begin(), itEnd=effects.end(); it!=itEnd; ++it)
{
float rnd = RandomGenerator.frand();
if (rndEffectArgFloat[0])
{
addedQty += it->EffectArgFloat[1];
PHRASE_UTILITIES::sendItemSpecialEffectProcMessage(ITEM_SPECIAL_EFFECT::ISE_FORAGE_ADD_RM, player, NULL, (sint32)(it->EffectArgFloat[1]*100.));
}
}
results[A] *= 1.0f + (float)addedQty;
}
}
// else
// {
// results[A] = 0;
// }
// Speed: always the requested speed (otherwise, looks like a bug for the player when it's the speed of the action)
results[S] = reqPosProps[S];
_S = (_S*nbe + results[S]) / (nbe + 1.0f); // average per source
// Quality: converges towards the requested value, except when a drop occurs (0 at this step)
float usedReqQ = (propDrop == Q) ? 0.0f : reqPosProps[Q] * qualityCeilingFactor;
float resQ = (results[Q]*qualitySlowFactor + usedReqQ) / (qualitySlowFactor+1);
float maxQOfSource = (float)_MaxQuality;
if ( resQ > maxQOfSource )
{
resQ = maxQOfSource;
//if ( results[Q] < (float)_MaxQuality )
// nldebug( "Quality limited by source to %hu", _MaxQuality ); // TODO: tell the player(s)
}
if ( (resQ < _Q) || (resQ < reqPosProps[Q]) || (! ForageQualityCeilingClamp.get()) )
{
// Set Q only if not increasing and exceeding requested quality
results[Q] = resQ;
}
else
{
// Clamp Q to the max required by the player
results[Q] = reqPosProps[Q];
}
float prevQ = _Q;
_Q = results[Q]; // now there is only one extractor => the Q of the source is the resulting Q
if ( ((prevQ < reqPosProps[Q]) && (_Q == reqPosProps[Q])) || ((prevQ < maxQOfSource) && (_Q == maxQOfSource)) )
{
setNewImpactScheme(); // we just reached the max quality
}
// Calc impact of the new average values
// Previously, the impact depended on the level of the extraction:
// float quantityBaseImpact = _A * _S * ForageQuantityImpactFactor.get();
// float qualityBaseImpact = (_Q - oldQ) * ForageQualityImpactFactor.get();
//
// Now it's constant (=max), but the amount of damage depends on the level of the extraction:
float quantityBaseImpact = MaxAS * ForageQuantityImpactFactor.get();
float qualityBaseImpact = MaxDeltaQ * ForageQualityImpactFactor.get();
uint impactScheme = _IImpactMappingScheme;
if ( impactScheme >= SpecialNewbieImpactSchemeD)
{
// Lower impacts for newbies
impactScheme -= SpecialNewbieImpactSchemeD;
quantityBaseImpact *= 0.5f;
qualityBaseImpact *= 0.5f;
}
if ( ForceAbsorption.get() != 0 )
{
absPosProps[A] = ForceAbsorption.get();
absPosProps[Q] = ForceAbsorption.get();
absPosProps[S] = ForceAbsorption.get();
}
for ( uint i=D; i!=NbRTProps; ++i )
{
if (i==E && _SafeSource)
break;
uint impactType = ImpactSchemes[impactScheme][i-NbPosRTProps];
float impact;
switch ( impactType )
{
case 0 : impact = quantityBaseImpact * (1.0f - absPosProps[A]); break;
case 1 : impact = qualityBaseImpact * (1.0f - absPosProps[Q]); break;
default: impact = (quantityBaseImpact + qualityBaseImpact) / 2.0f * (1.0f - absPosProps[S]); break; // bound on the average of both, absorption of S
}
impact += RandomGenerator.frandPlusMinus( impact ); // result impact from 0 to impact*2
// add spire effect ( aggressivity )
if ( player )
{
const CSEffect* pEffect = player->lookForActiveEffect( EFFECT_FAMILIES::TotemHarvestAgg );
if ( pEffect != NULL )
{
impact *= ( 1.0f - pEffect->getParamValue() / 100.0f );
}
}
if ( impact < 0 ) impact = 0; // impact can't be negative
if ( (i==D) && (lifeAbsorberRatio != 0) )
{
// Damage the life absorber, instead of impacting D
CEntityBase *entity = CEntityBaseManager::getEntityBasePtr( extractingEntityRow ); // getEntityBasePtr() tests TheDataset.isAccessible( extractingEntity )
if ( entity )
{
float impactOnHP = ((float)lifeAbsorberRatio) * impact * 0.01f;
impact -= impactOnHP;
float dmgRatio = impactOnHP * ForageHPRatioPerSourceLifeImpact.get();
sint32 dmg = (sint32)((float)entity->maxHp() * dmgRatio);
if ( dmg != 0 )
CHarvestSource::hitEntity( RYZOMID::forageSource, entity, dmg, dmg, true );
}
}
if ( (_TargetRTProps[i-D] < CommonRiskThreshold*0.90f) && (_TargetRTProps[i-D] + impact > CommonRiskThreshold) )
{
// Avoid a brutal unnatural end, make a step just before reaching threshold
impactRTProp( (TTargetRTProp)(i-D), CommonRiskThreshold - 2.0f );
}
else
{
// Normal impact
impactRTProp( (TTargetRTProp)(i-D), _TargetRTProps[i-D] + impact );
}
}
}
/*
* Update the source state with a care (see doc in .h)
*/
void CHarvestSource::takeCare( float *deltas, bool *isUseful )
{
H_AUTO(CHarvestSource_takeCare);
// Do not count care if from the "begin zone"
if ( _TargetRTProps[TargetD] > ForageCareBeginZone.get() )
*isUseful = true;
if ( _TargetRTProps[TargetE] > ForageCareBeginZone.get() )
*isUseful = true;
//if ( deltas[DeltaD] > ForageCareBeginZone.get() )
// *isUseful = true;
// Calc actual deltas
deltas[DeltaD] += RandomGenerator.frandPlusMinus( deltas[DeltaD]*0.05f );
deltas[DeltaE] += RandomGenerator.frandPlusMinus( deltas[DeltaE]*0.05f );
//deltas[DeltaC] += RandomGenerator.frandPlusMinus( deltas[DeltaC]*0.05f );
// TODO: impact on S,A,Q
// Apply deltas
float targetValue = _TargetRTProps[TargetD] - (deltas[DeltaD] * ForageCareFactor.get());
if ( targetValue < 0 )
targetValue = 0;
impactRTProp( TargetD, targetValue );
if (!_SafeSource)
{
targetValue = _TargetRTProps[TargetE] - (deltas[DeltaE] * ForageCareFactor.get());
if ( targetValue < 0 )
targetValue = 0;
impactRTProp( TargetE, targetValue );
}
//_C -= (deltas[DeltaC] * ForageCareFactor.get());
//if ( _C < 0 ) _C = 0;
}
/*
* When the threshold of E is reached.
*/
void CHarvestSource::makeDamagingEvent()
{
H_AUTO(CHarvestSource_makeDamagingEvent);
sint32 r = RandomGenerator.rand( 1 );
if ( r == 0 )
{
spawnToxicCloud();
}
else
{
explode();
}
_Events.clear();
setNewImpactScheme();
}
/*
*
*/
class CForageDamagingEventRangeSelector : public CRangeSelector
{
public:
void buildTargetList( sint32 x, sint32 y, float radius /*, float minFactor*/ )
{
H_AUTO(CForageDamagingEventRangeSelector_buildTargetList);
buildDisc( 0, x, y, radius, EntityMatrix, true );
}
float getFactor( uint entityIdx )
{
return 1.0f; // every entity in the radius gets the same damage, not depending of his location
} // improvement note: for explosion, could be decreasing with the distance
};
/*
* A continuous damaging event
*/
void CHarvestSource::spawnToxicCloud()
{
H_AUTO(CHarvestSource_spawnToxicCloud);
sendMessageToExtractors( "SOURCE_TOXIC_CLOUD" );
// Get random cloud params near the source (TODO: Z)
float dmgFactor = getDamageFactor();
sint32 iRadius = min( (sint32)2, (sint32)(dmgFactor * 3.0f) ); // => mapping [0..1] to {0, 1, 2}
float Radiuses [3] = { 1.5f, 3.0f, 5.0f }; // corresponding to the 3 sheets
float radius = Radiuses[iRadius];
CVector cloudPos( _Pos.x, _Pos.y, 0.0f );
if ( iRadius != 0 )
{
// For a big toxic cloud, shift the centre in a axis-aligned square of 4 m width (max dist = 2.8 m)
const float MaxAxisDistFromSource = 2.0f;
float dX = RandomGenerator.frand( MaxAxisDistFromSource*2.0f );
float dY = RandomGenerator.frand( MaxAxisDistFromSource*2.0f );
cloudPos.x += dX - MaxAxisDistFromSource;
cloudPos.y += dY - MaxAxisDistFromSource;
}
// Spawn the toxic cloud
CToxicCloud *tc = new CToxicCloud();
tc->init( cloudPos, radius, (sint32)(dmgFactor * ToxicCloudDamage.get()), ToxicCloudUpdateFrequency );
CSheetId sheet( toString( "toxic_cloud_%d.fx", iRadius ));
if ( tc->spawn( sheet ) )
{
CEnvironmentalEffectManager::getInstance()->addEntity( tc );
#ifdef NL_DEBUG
nldebug( "FG: Toxic cloud spawned (radius %g)", radius );
#endif
}
else
{
nlwarning( "FG: Unable to spawn toxic cloud (mirror range full?)" );
delete tc;
}
}
/*
* Test which entities to hit (all entities except NPC and non-pets creatures)
*/
inline bool isAffectedByForageDamage( CEntityBase *entity )
{
uint8 entityType = entity->getId().getType();
return ! ((entityType == RYZOMID::npc) ||
((entityType == RYZOMID::creature) &&
(entity->getRace() != EGSPD::CPeople::MektoubMount) &&
(entity->getRace() != EGSPD::CPeople::MektoubPacker)));
}
/*
* A one-time damaging event
*/
void CHarvestSource::explode()
{
H_AUTO(CHarvestSource_explode);
sendMessageToExtractors( "SOURCE_EXPLOSION" );
// Set the FX
CMirrorPropValue visualFx( TheDataset, _DataSetRow, DSPropertyVISUAL_FX );
visualFx = (visualFx() | 0x400); // set bit 10
_ExplosionResetCounter = ExplosionResetPeriod;
// Get entities around the source, and hit them
if ( HarvestAreaEffectOn )
{
// Calculate damage
float fDmg = getDamageFactor() * ForageExplosionDamage.get();
float dmgAvoided = fDmg;
for ( CRDEvents::const_iterator iev=_Events.begin(); iev!=_Events.end(); ++iev )
{
const CReduceDamageEvent& event = (*iev);
if ( CTickEventHandler::getGameCycle() - event.Time < ForageReduceDamageTimeWindow.get() )
{
//nldebug( "Damage %.1f x %.1f", fDmg, event.Ratio );
fDmg *= event.Ratio; // multiple events are multiplied (e.g. 50% and 50% again do 25%)
}
}
dmgAvoided = dmgAvoided - fDmg;
bool wereAllEventsMissed = (! _Events.empty()) && (dmgAvoided == 0);
// Make area of effect
const float explosionRadius = 4.0f;
CForageDamagingEventRangeSelector targetSelector;
targetSelector.buildTargetList( (sint32)(_Pos.x * 1000.0f), (sint32)(_Pos.y * 1000.0f), explosionRadius );
const vector& targets = targetSelector.getEntities();
for ( vector::const_iterator it=targets.begin(); it!=targets.end(); ++it )
{
CEntityBase *entity = (*it);
if ( entity && isAffectedByForageDamage( entity ) )
{
sint32 dmg = (sint32)(entity->getActualDamageFromExplosionWithArmor( fDmg ));
CHarvestSource::hitEntity( RYZOMID::forageSource, entity, dmg, (sint32)(fDmg+dmgAvoided), false, (sint32)dmgAvoided ); // is not blocked by any armor
if ( wereAllEventsMissed && (entity->getId().getType() == RYZOMID::player) )
{
PHRASE_UTILITIES::sendDynamicSystemMessage( entity->getEntityRowId(), "SOURCE_DMG_REDUX_MISSED" );
}
}
}
}
}
/*
* Reduce the damage of the next blowing up (if it is in near time delta)
*/
void CHarvestSource::reduceBlowingUpDmg( float ratio )
{
H_AUTO(CHarvestSource_reduceBlowingUpDmg);
CReduceDamageEvent event;
event.Time = CTickEventHandler::getGameCycle();
event.Ratio = ratio;
_Events.push_back( event );
}
/*
* Get the damage factor of a source (for explosion or toxic cloud)
*/
float CHarvestSource::getDamageFactor() const
{
H_AUTO(CHarvestSource_getDamageFactor);
// Map linearly using 5 -> 0.5, 80 -> 1.0 (to match previous algorithm)
float statQualityFactor = ((((float)getStatQuality())+70.0f) / 150.0f);
switch ( ImpactSchemes[_IImpactMappingScheme][E] )
{
case 0 : return _A * _S * statQualityFactor / MaxAS; break;
case 1 : return _Q * statQualityFactor / MaxQ; break; // not using DeltaQ but Q
default: return (_A*_S/MaxAS + _Q/MaxQ) * statQualityFactor / 2; break;
}
}
/*
* Return the stat quality of the raw material
*/
/*CHarvestSource::TStatClassForage CHarvestSource::getStatQuality() const
{
H_AUTO(CHarvestSource_getStatQuality);
const CStaticItem *itemInfo = CSheets::getForm( _MaterialSheet );
if ( itemInfo && itemInfo->Mp )
{
// (0..20 - 1) / 20 = 0
// (21..40 - 1) / 20 = 1
// (41..51 - 1) / 20 = 2;
// (52..59) / 20 = 2
// (61..79) / 20 = 3
// (80..99) / 20 = 4
// (100...) -> 4
sint16 statEnergy = (sint16)(itemInfo->Mp->StatEnergy);
TStatClassForage sq =
(statEnergy < 51) ? (statEnergy - 1) / 20 :
((statEnergy < 100) ? statEnergy / 20 : SupremeMagnificient);
return sq;
}
nlwarning( "Invalid raw material %s", _MaterialSheet.toString().c_str() );
return BasicPlainAverage;
}*/
/*
* Return the stat quality of the raw material [0..100]
* (Frequent values: 20 35 50 65 80)
*/
uint16 CHarvestSource::getStatQuality() const
{
H_AUTO(CHarvestSource_getStatQuality);
const CStaticItem *itemInfo = CSheets::getForm( _MaterialSheet );
if ( itemInfo && itemInfo->Mp )
{
return itemInfo->Mp->StatEnergy;
}
nlwarning( "Invalid raw material %s", _MaterialSheet.toString().c_str() );
return 0;
}
/*
* Damage an entity
*/
void CHarvestSource::hitEntity( RYZOMID::TTypeId aggressorType, CEntityBase *entity, sint32 hpDamageAmount, sint32 hpDamageAmountWithoutArmour, bool isIntentional, sint32 hpAvoided )
{
H_AUTO(CHarvestSource_hitEntity);
if ( entity->isDead())
return;
bool killed = entity->changeCurrentHp( -hpDamageAmount );
if ( isIntentional )
{
SM_STATIC_PARAMS_1(params, STRING_MANAGER::integer);
params[0].Int = hpDamageAmount;
PHRASE_UTILITIES::sendDynamicSystemMessage( entity->getEntityRowId(), "FORAGE_ABSORB_DMG", params );
}
else
PHRASE_UTILITIES::sendNaturalEventHitMessages( aggressorType, entity->getEntityRowId(), hpDamageAmount, hpDamageAmountWithoutArmour, hpAvoided );
if ( killed )
PHRASE_UTILITIES::sendDeathMessages( TDataSetRow(), entity->getEntityRowId() );
}
NLMISC_VARIABLE( sint32, ForageSourceDisplay, "Row index of source to verbose" );
/*
* Testing
*/
TDataSetRow TestSourceRow;
void forageTestDoBegin()
{
CHarvestSource templateSource, *testSource;
templateSource.setLifetime( 1140 );
templateSource.setProspectionExtraExtractionTime( 1140 );
CStaticDepositRawMaterial rm;
testSource = new CHarvestSource;
CDeposit deposit;
testSource->init( templateSource, CVector2f(1000.0f,1000.0f), NULL, &deposit, &rm, 1.0f );
TDataSetRow dsr;
if ( testSource->spawnBegin( 0, dsr, false ) )
{
TestSourceRow = testSource->rowId();
}
else
{
delete testSource;
}
}
bool forageTestDoExtract(
NLMISC::CLog& log,
uint nbIterations,
float reqPeriod,
float reqA,
float reqQ,
float absorption,
float successFactor )
{
CHarvestSource *testSource = CHarvestSourceManager::getInstance()->getEntity( TestSourceRow );
if ( ! testSource )
{
log.displayNL( "Call forageTestBegin first" );
return true;
}
// Request and output results
FILE *f = fopen( std::string(getLogDirectory() + "forage_test.csv").c_str(), "at" );
FILE *f2 = fopen( std::string(getLogDirectory() + "forage_test.log").c_str(), "at" );
float reqS = 1.0f / (reqPeriod * 10.0f);
float req [CHarvestSource::NbPosRTProps];
float abs [CHarvestSource::NbPosRTProps];
float res [CHarvestSource::NbPosRTProps];
static bool FirstTime = true;
req[CHarvestSource::S] = reqS;
req[CHarvestSource::A] = reqA;
req[CHarvestSource::Q] = reqQ;
abs[CHarvestSource::S] = absorption;
abs[CHarvestSource::A] = absorption;
abs[CHarvestSource::Q] = absorption;
res[CHarvestSource::S] = 0.025f;
res[CHarvestSource::A] = 0.0f;
res[CHarvestSource::Q] = 0.0f;
if ( FirstTime )
{
FirstTime = false;
fprintf( f, "A;Q;D;E;C;reqS;reqA;reqQ;qty;scheme;limit;\n" );
}
testSource->beginExtraction( TDataSetRow::createFromRawIndex( INVALID_DATASET_INDEX ), false );
bool eventD = false, eventE = false, eventC = false;
for ( uint i=0; i!=nbIterations; ++i )
{
TDataSetRow row;
CHarvestSource::TRealTimeProp propDrop;
testSource->extractMaterial( req, abs, ForageQualityCeilingFactor.get(), ForageQualitySlowFactor.get(), res, successFactor, 0, row, propDrop );
fprintf( f, "%g;%g;%g;%g;%g;%g;%g;%g;%g;%u;%u;\n",
res[CHarvestSource::A], res[CHarvestSource::Q],
testSource->getD(), testSource->getE(), 0.f /*testSource->getC()*/,
reqS, reqA, reqQ,
testSource->quantity(), testSource->getImpactScheme()*5, 127 );
if ( (!eventD) && (testSource->getD() > 127) )
{
fprintf( f2, "D: %u\n", i );
eventD = true;
}
if ( (!eventE) && (testSource->getE() > 127) )
{
fprintf( f2, "E: %u\n", i );
eventE = true;
}
/*if ( (!eventC) && (testSource->getC() > 127) )
{
fprintf( f2, "C: %u\n", i );
eventC = true;
}*/
}
if ( !eventD )
fprintf( f2, "D---\n" );
if ( !eventE )
fprintf( f2, "E---\n" );
if ( !eventC )
fprintf( f2, "C---\n" );
fclose( f );
fclose( f2 );
return true;
}
void forageTestDoEnd()
{
CHarvestSource *testSource = CHarvestSourceManager::getInstance()->getEntity( TestSourceRow );
if ( ! testSource )
return;
CHarvestSourceManager::getInstance()->destroyEntity( testSource->rowId() );
testSource = NULL;
}
NLMISC_COMMAND( forageTestBegin, "Start forage test", "" )
{
forageTestDoBegin();
return true;
}
NLMISC_COMMAND( forageTestExtract, "Make a test extraction (floats in percent)",
" " )
{
// Read args
sint n = (sint)args.size();
uint nbIterations = 1;
float reqPeriod = 2.0f;
float reqA = 2.0f;
float reqQ = 50.0f;
float absorption = 0.1f;
float successFactor = 1.0f;
if ( n > 0 )
{
NLMISC::fromString(args[0], nbIterations);
if ( n > 1 )
{
NLMISC::fromString(args[1], reqPeriod);
if ( n > 2)
{
NLMISC::fromString(args[2], reqA);
if ( n > 3 )
{
NLMISC::fromString(args[3], reqQ);
if ( n > 4)
{
NLMISC::fromString(args[4], absorption);
absorption /= 100.0f;
if ( n > 5 )
{
NLMISC::fromString(args[5], successFactor);
successFactor /= 100.0f;
}
}
}
}
}
}
return forageTestDoExtract( log, nbIterations, reqPeriod, reqA, reqQ, absorption, successFactor );
}
NLMISC_COMMAND( forageTestBatch, "Batch forage tests", "" )
{
uint nbIterations = 15;
float reqPeriod;
float reqA;
float reqQ;
float absorption;
float successFactor = 1.0f;
for ( absorption=0.1f; absorption<=0.8f; absorption+=0.7f )
{
forageTestDoBegin();
for ( reqQ=1.0f; reqQ<=251.0f; reqQ+=50.0f )
{
for ( reqA=1.0f; reqA<=5.0f; reqA+=2.0f )
{
for ( reqPeriod=2.2f; reqPeriod>=0.2f; reqPeriod-=0.5f )
{
forageTestDoExtract( log, nbIterations, reqPeriod, reqA, reqQ, absorption, successFactor );
CHarvestSource *testSource = CHarvestSourceManager::getInstance()->getEntity( TestSourceRow );
if ( testSource )
{
CHarvestSource templateSource;
templateSource.setLifetime( 1140 );
templateSource.setProspectionExtraExtractionTime( 1140 );
testSource->resetSource( templateSource );
testSource->setN( 120 );
testSource->setD( 0 );
testSource->setE( 0 );
//testSource->setC( 0 );
}
}
}
}
forageTestDoEnd();
}
return true;
}
NLMISC_COMMAND( forageTestEnd, "End forage test", "" )
{
// TODO: despawn spawned source!
forageTestDoEnd();
return true;
}
NLMISC_DYNVARIABLE( uint, NbForageSources, "Number of forage sources" )
{
if ( get )
*pointer = CHarvestSourceManager::getInstance()->nbEntities();
}
NLMISC_VARIABLE( uint, NbAutoSpawnedForageSources, "Number of auto-spawned forage sources" );