Add infos into target window
[ryzomcore.git] / ryzom / server / src / entities_game_service / harvest_source.cpp
blobbd4da990b3e422423b5a8bf6d1b7af16fad366c7
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2013 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
6 //
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "stdpch.h"
23 #include "harvest_source.h"
24 #include "deposit.h"
25 #include "egs_globals.h"
26 #include "phrase_manager/phrase_utilities_functions.h"
27 #include "entity_manager/entity_base.h"
28 #include "player_manager/player_manager.h"
29 #include "player_manager/player.h"
30 #include "player_manager/character.h"
31 #include "forage_progress.h"
32 #include "range_selector.h"
33 #include "egs_sheets/egs_sheets.h"
34 #include "nel/misc/variable.h"
35 #include "phrase_manager/s_effect.h"
36 #include "server_share/r2_vision.h"
39 using namespace NLMISC;
40 using namespace NLNET;
41 using namespace std;
43 NL_INSTANCE_COUNTER_IMPL(CHarvestSource);
44 NL_INSTANCE_COUNTER_IMPL(CHarvestSourceManager);
46 NL_ISO_TEMPLATE_SPEC CSimpleEntityManager<CHarvestSource> *CSimpleEntityManager<CHarvestSource>::_Instance = NULL;
48 uint NbAutoSpawnedForageSources = 0;
51 const NLMISC::TGameCycle MaxT = 1200; // 2 min for prospected sources (TODO: external data)
52 const float CommonRiskThreshold = 127.0f;
53 const float ThresholdD1 = CommonRiskThreshold;
54 const float ThresholdD2 = CommonRiskThreshold * 1.20f;
55 const float ThresholdE = CommonRiskThreshold;
56 const float ThresholdC = CommonRiskThreshold;
58 const float DeltaMoveBarPerTick = (DeltaMoveBarPerSec / 10.0f);
59 const float DeltaResetBarPerTick = (DeltaResetBarPerSec / 10.0f);
61 sint32 ForageSourceDisplay = -1;
63 static const NLMISC::TGameCycle Locked = ~0;
64 static const NLMISC::TGameCycle IniTime = (~0)-1; // highest positive integer (suitable for unsigned)
67 // Minimum extraction session:
68 // 6 extractions = 24 seconds with 4 seconds per extraction
69 // Maximum extraction session:
70 // 80 extractions = 120 seconds with 1.5 second per extraction
74 // MaxA: 3.5
75 // MaxS: 1/15
76 // nlctassert(MaxAS>0);
77 const float MaxAS = (3.5f / 15.0f);
79 // MaxRequiredQ: 250; InitResultQ: 1 => MaxDeltaQ: 26
80 // nlctassert(MaxDeltaQ>0);
81 const float MaxQ = 250.0f;
82 const float MaxDeltaQ = 26.0f;
84 // { D, E, C } impacted by (0=Qtty, 1=Qlty, 2=Both)
85 uint ImpactSchemes [6][3] = { { 0, 1, 2 }, { 0, 2, 1 }, { 1, 0, 2 }, { 1, 2, 0 }, { 2, 0, 1 }, { 2, 1, 0 } };
86 // Observed impact on D (/10): 1 3 6 10 3 1
87 // Observed impact on E (/10): 6 3 1 1 3 10
88 // Note: if modifying this schemes, please change FORAGE_SOURCE_IMPACT_MODE in phrase_en.txt.
89 uint16 SpecialNewbieImpactSchemeD = 10;
90 uint16 LowDangerMappings[2] = { (uint16)(SpecialNewbieImpactSchemeD + 1), (uint16)(SpecialNewbieImpactSchemeD + 4) };
92 sint8 ExplosionResetPeriod = 50; // 5 s
94 CHarvestSource AutoSpawnSourceIniProperties;
97 * Access to singleton
99 CHarvestSourceManager *CHarvestSourceManager::getInstance()
101 return (CHarvestSourceManager*)_Instance;
105 * Initialization of source manager
107 void CHarvestSourceManager::init( TDataSetIndex baseRowIndex, TDataSetIndex size )
109 CSimpleEntityManager<CHarvestSource>::init( baseRowIndex, size );
111 // Note: Now, most of these values are overridden by deposit settings (see CFgProspectionPhrase::autoSpawnSource())
112 AutoSpawnSourceIniProperties.setLifetime( 6000 ); // 10 min
113 AutoSpawnSourceIniProperties.setProspectionExtraExtractionTime( 0 ); // no extra time
114 //AutoSpawnSourceIniProperties.setDistVis( 100 );
117 void CHarvestSourceManager::release()
119 delete (CHarvestSourceManager*)_Instance;
123 * HarvestSource constructor
125 CHarvestSource::CHarvestSource()
127 _NbExtractions = 0;
128 _ForageSite = NULL;
129 _DepositForK = NULL;
131 // Set default values (TODO: external data)
132 _LifeTime = 200; // 20 s
133 _ExtractionTime = 250; // 25 s for initial extraction time (now, recalculated using quality of material)
134 _ExtraExtractionTime = 25; // 2.5 s
135 _IncludedBonusExtractionTime = 0;
136 _T = Locked; // until spawn is completed
137 _S = 0.025f; // 4 s per delivery (initial value may be not used by the extraction action)
138 _A = 0.1f;
139 _Q = 1.0f;
140 _D = 0;
141 _E = 0;
142 _TargetRTProps[TargetD] = 0;
143 _TargetRTProps[TargetE] = 0;
144 //_C = 0;
145 _MaxQuality = ~0;
146 _IImpactMappingScheme = 0;
147 _N = 0;
148 _IsInNewbieMode = false;
149 _BonusForAPct = 0;
150 //_DistanceVisibility = 80;
151 //_StealthVisibility = VISIBILITY_RIGHTS::All;
152 _IsExtractionInProgress = false;
153 _ExplosionResetCounter = -1;
154 _IsAutoSpawned = false;
155 _SafeSource = false;
156 _NbEventTriggered = 0;
161 * HarvestSource destructor
163 CHarvestSource::~CHarvestSource()
165 // unregister the deposit auto spawn if any
166 setDepositAutoSpawn(NULL);
171 * setDepositAutoSpawn
173 void CHarvestSource::setDepositAutoSpawn(CDeposit *deposit)
175 // unregister the current deposit, if any
176 if(_DepositAutoSpawn)
178 _DepositAutoSpawn->decreaseAutoSpawnedSources();
179 _DepositAutoSpawn= NULL;
182 // register the new one if any
183 if(deposit)
185 _DepositAutoSpawn= deposit;
186 _DepositAutoSpawn->increaseAutoSpawnedSources();
192 * Init the source. All pointers must be valid (but forageSite may be NULL).
193 * Return false if the current quantity in the deposit is 0.
195 bool CHarvestSource::init( const CHarvestSource& ini, const NLMISC::CVector2f& pos,
196 CRecentForageSite *forageSite,
197 CDeposit *depositForK,
198 const CStaticDepositRawMaterial *rmInfo, float quantityRatio )
200 initFrom( ini );
201 setPos( pos );
202 setForageSite( forageSite );
203 bool isNonEmpty = setRawMaterial( rmInfo, quantityRatio );
205 // Set link to deposit for kami anger level
206 _DepositForK = depositForK;
208 return isNonEmpty;
213 * Set the raw material, the initial amount and the max quality, or return false if the current quantity in the deposit is 0.
215 bool CHarvestSource::setRawMaterial( const CStaticDepositRawMaterial *rmInfo, float quantityRatio )
217 H_AUTO(CHarvestSource_setRawMaterial);
219 _MaterialSheet = rmInfo->MaterialSheet;
221 // Get corresponding initial quantity & max quality
222 const CStaticItem *staticItem = CSheets::getForm( _MaterialSheet );
223 if ( staticItem && staticItem->Mp )
225 /// The quantity is a fraction of the 'Stackable' property (but it is limited by the constraints of the deposit)
226 _N = min( max( (((float)staticItem->Stackable) * quantityRatio), 1.0f ), _ForageSite->getQuantityInDeposit() );
228 // Select either the MaxQuality in the deposit primitive or in the RM sheet (if -1 in the deposit, or no deposit)
229 sint16 depositMaxQuality = _ForageSite ? _ForageSite->deposit()->maxQuality() : -1;
230 if ( depositMaxQuality != -1 )
231 _MaxQuality = (uint16)depositMaxQuality;
232 else
233 _MaxQuality = staticItem->Mp->MaxQuality;
234 //nldebug( "Quality limited by source to %hu", _MaxQuality );
236 if ( _N == 0 )
237 return false;
239 else
241 nlwarning( "%s is not a valid raw material sheet", _MaterialSheet.toString().c_str() );
242 return false;
244 return true;
249 * Recalculate the remaining extraction time depending on the requested quality.
250 * Does nothing if the source is in "extra extraction time".
251 * _ExtraExtractionTime is included in _ExtraExtractionTime.
253 * Precondition: _ExtractionTime != 0
255 void CHarvestSource::recalcExtractionTime( float requestedQuality )
257 if ( _T > _ExtraExtractionTime )
259 nlassert( _ExtractionTime > _ExtraExtractionTime );
260 float timeRatio = ((float)_T) / ((float)_ExtractionTime);
261 //float fExtractionTime = requestedQuality*(0.3092f*10.0f) + (22.0f*10.0f); // Q10 -> 25 s; Q250 -> 1'40 s
262 float fExtractionTime = requestedQuality*ForageExtractionTimeSlopeGC.get() + ForageExtractionTimeMinGC.get() + (float)_ExtraExtractionTime;
263 _ExtractionTime = (NLMISC::TGameCycle)fExtractionTime;
264 _T = (NLMISC::TGameCycle)(fExtractionTime * timeRatio);
270 * Prepare the source as an unpublished entity in mirror. Return false in case of failure.
271 * Must be called *after* init().
272 * prospectorDataSetRow must be either null (isNull()) or an accessible row (isAccessible()).
274 bool CHarvestSource::spawnBegin( uint8 knowledgePrecision, const TDataSetRow& prospectorDataSetRow, bool isAutoSpawned )
276 H_AUTO(CHarvestSource_spawn);
278 // Add into mirror (but unpublished)
279 CEntityId entityId = CEntityId::getNewEntityId( RYZOMID::forageSource );
280 if ( ! Mirror.createEntity( entityId ) )
281 return false;
282 _DataSetRow = TheDataset.getDataSetRow( entityId );
283 _IsAutoSpawned = isAutoSpawned;
285 // Set the sheet id (including knowledge information)
286 uint sourceFXIndex = _ForageSite ? (uint)_ForageSite->deposit()->sourceFXIndex() : 0;
287 CMirrorPropValue<TYPE_SHEET> sheet( TheDataset, _DataSetRow, DSPropertySHEET );
288 sheet = CSheetId( toString( "%u_%u.forage_source", sourceFXIndex, knowledgePrecision ) ).asInt();
290 // For knowledge 3, fit the sheet id of the RM sitem into NAME_STRING_ID (instead of a string id) !
291 CMirrorPropValue<TYPE_NAME_STRING_ID> nameId( TheDataset, _DataSetRow, DSPropertyNAME_STRING_ID );
292 nameId = (knowledgePrecision == 3) ? _MaterialSheet.asInt() : 0;
293 if ( knowledgePrecision != 0 )
295 const CStaticItem *staticItem = CSheets::getForm( materialSheet() );
296 if ( staticItem && staticItem->Mp )
298 // Set additional knowledge info into VISUAL_FX
299 CMirrorPropValue<TYPE_VISUAL_FX> visualFx( TheDataset, _DataSetRow, DSPropertyVISUAL_FX );
300 switch ( knowledgePrecision )
302 case 1: visualFx = (TYPE_VISUAL_FX)staticItem->Mp->getGroup(); break; // initializes the prop, but only 10 bits allowed
303 case 2: // family sent for 2 & 3 (for knowledge 3, used only as icon index)
304 case 3: visualFx = (TYPE_VISUAL_FX)staticItem->Mp->Family; break; // initializes the prop, but only 10 bits allowed
305 default:; // default value is 0
307 if ( (visualFx() & 0x400) != 0 )
309 nlwarning( "FG: Family or group index exceeding max!" ); // bit 10 is reserved for explosion FX
310 visualFx = 0;
315 // Set the target (the prospector, or a nul datasetrow if auto-spawned)
316 CMirrorPropValue<TYPE_TARGET_ID> targetRow( TheDataset, _DataSetRow, DSPropertyTARGET_ID );
317 targetRow = prospectorDataSetRow;
319 // Add to manager so that update() will be called at each game cycle
320 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
322 return true;
327 * Complete the source spawn: publish the entity in mirror or delete it.
328 * Caution: if authorized, the source object is deleted!
330 void CHarvestSource::spawnEnd( bool authorized )
332 if ( ! authorized )
334 despawn();
335 CHarvestSourceManager::getInstance()->destroyEntity( _DataSetRow );
336 return;
339 // Unlock update
340 _ExtractionTime = IniTime; // will trigger calculation in recalcExtractionTime() because _T > _ExtraExtractionTime
341 _T = _ExtractionTime; // ratio 1.0 => beginning of extraction time
343 if ( _IsAutoSpawned )
344 ++NbAutoSpawnedForageSources;
346 // Set the initial position
347 CMirrorPropValue<TYPE_POSX> posX( TheDataset, _DataSetRow, DSPropertyPOSX );
348 CMirrorPropValue<TYPE_POSY> posY( TheDataset, _DataSetRow, DSPropertyPOSY );
349 posX = (TYPE_POSX)(_Pos.x * 1000.0f);
350 posY = (TYPE_POSY)(_Pos.y * 1000.0f);
352 // Set the WhoSeesMe bitfield (distance of visibility by players (0-31) and creatures (32-63)) (now constant)
353 uint32 nbBitsOn = /*_DistanceVisibility*/80 * 32 / 250; // (250 m is the max, corresponding to 32 bits on
354 const uint64 distanceBitfield = IsRingShard? R2_VISION::buildWhoSeesMe(R2_VISION::WHOSEESME_VISIBLE_MOB,false): ((uint64)1 << (uint64)nbBitsOn) - 1; // (hide to creatures)
355 CMirrorPropValue<TYPE_WHO_SEES_ME> whoSeesMe(TheDataset, _DataSetRow, DSPropertyWHO_SEES_ME );
356 whoSeesMe = distanceBitfield;
358 // Set selectable property to true
359 CMirrorPropValue<TYPE_CONTEXTUAL> contextualProperties(TheDataset, _DataSetRow, DSPropertyCONTEXTUAL );
360 CProperties prop(0);
361 prop.selectable(true);
362 contextualProperties = prop;
364 // Update bars and other variable properties
365 updateVisuals();
367 // Publish in mirror
368 TheDataset.declareEntity( _DataSetRow );
373 * Return the prospector or a null datasetrow if there was no prospection (auto-spawn)
374 * The accessibility of this datasetrow must be checked before use.
376 const TDataSetRow& CHarvestSource::getProspectorDataSetRow() const
378 CMirrorPropValueRO<TYPE_TARGET_ID> targetRow( TheDataset, _DataSetRow, DSPropertyTARGET_ID );
379 return targetRow();
384 * Despawn the source in mirror, and exit from forage site
386 void CHarvestSource::despawn()
388 H_AUTO(CHarvestSource_despawn);
390 // Remove from mirror
391 CEntityId entityId = TheDataset.getEntityId( _DataSetRow );
392 Mirror.removeEntity( entityId );
394 //nldebug( "--- %p from %p", this, _ForageSite );
396 // Exit from forage site (the entering is done outside class, because a failure may prevent to create the source)
397 if ( _ForageSite )
398 _ForageSite->removeActiveSource();
400 if ( _IsAutoSpawned )
401 --NbAutoSpawnedForageSources;
403 // End forage sessions and calculate XP and give result
404 if ( ! _Foragers.empty() )
406 // The first forager is the only one who can extract. Give result & dispatch XP
407 CForagers::const_iterator it = _Foragers.begin();
408 CCharacter *player = PlayerManager.getChar( *it );
409 if ( player && player->forageProgress() )
411 if ( player->forageProgress()->sourceRowId() == _DataSetRow ) // check if he has not changed his target
413 player->giveForageSessionResult( this );
417 // End sessions of care takers (all excluding the first element)
418 for ( ++it; it!=_Foragers.end(); ++it )
420 player = PlayerManager.getChar( *it );
421 if ( player && player->forageProgress() )
423 if ( player->forageProgress()->sourceRowId() == _DataSetRow ) // check if he has not changed his target
425 player->endForageSession();
434 * Helper for updateVisiblePostPos()
436 inline void updateBarValueAtTick( float& destValue, float& currentValue )
438 float diff = destValue - currentValue;
439 if ( diff > 0 )
441 currentValue = std::min( destValue, currentValue + DeltaMoveBarPerTick );
443 else if ( diff < 0 )
445 float delta = (currentValue == 0) ? DeltaResetBarPerTick : DeltaMoveBarPerTick;
446 currentValue = std::max( destValue, currentValue - delta );
452 * Update
454 bool CHarvestSource::update()
456 H_AUTO(CHarvestSource_update);
458 if ( _IsExtractionInProgress )
460 // Make the bar transitions smooth (needs to be done on server to match timing on client)
461 updateBarValueAtTick( _TargetRTProps[TargetD], _D );
462 updateBarValueAtTick( _TargetRTProps[TargetE], _E );
464 // Test damaging event risk
465 if ( _E > ThresholdE )
467 makeDamagingEvent();
468 setEventTriggered();
469 impactRTProp( TargetE, 0 );
471 // Test spawn risk
472 /*if ( _C > ThresholdC )
474 //sendMessageToExtractors( "CREATURE_SPAWN" );
475 // TODO
476 //setEventTriggered();
477 _C = 0;
480 // Test depletion risk (if the bar transition is still in progress, wait)
481 if ( (_D > ThresholdD1) )
483 // if high risk value, deplete all the forage site (if exist and allow depletion)
484 if ( _ForageSite && _ForageSite->allowDepletionRisk() && _TargetRTProps[TargetD] > ThresholdD2 )
486 sendMessageToExtractors( "FORAGE_SOURCE_SITE_DEPLETED" );
487 _ForageSite->depleteAll();
489 // else will just kill the current source (send appropriate message)
490 else
491 sendMessageToExtractors( "FORAGE_SOURCE_DEPLETED" );
492 // in all case kill the current source, and add bad event
493 setEventTriggered();
494 despawn();
495 return false;
497 // Test remaining time
498 else if ( _T == 0 )
500 despawn();
501 return false;
503 else
505 --_T;
506 float naturalMoveThreshold = CommonRiskThreshold - 2.0f;
507 if ( _TargetRTProps[TargetD]+ForageExtractionNaturalDDeltaPerTick.get() < naturalMoveThreshold ) // don't auto-move if it makes it trigger the event
508 impactRTProp( TargetD, _TargetRTProps[TargetD] + ForageExtractionNaturalDDeltaPerTick.get() );
509 if (!_SafeSource)
511 if ( _TargetRTProps[TargetE]+ForageExtractionNaturalEDeltaPerTick.get() < naturalMoveThreshold )
512 impactRTProp( TargetE, _TargetRTProps[TargetE] + ForageExtractionNaturalEDeltaPerTick.get() );
514 if ( _DataSetRow.getIndex() == (TDataSetIndex)ForageSourceDisplay )
515 nldebug( "T: %u", _T );
516 updateVisuals();
517 return true;
520 else
522 // Remain locked if the source is not fully spanwed yet
523 if ( _T == Locked )
524 return true;
526 // Test end of lifetime
527 if ( _LifeTime == 0 )
529 despawn();
530 return false;
532 else
534 --_LifeTime;
535 if ( _DataSetRow.getIndex() == (TDataSetIndex)ForageSourceDisplay )
536 nldebug( "_LifeTime: %u", _LifeTime );
537 return true;
544 * Update visual properties & related stuff
546 void CHarvestSource::updateVisuals()
548 H_AUTO(CHarvestSource_updateVisuals);
550 // Set the bars on the entity
551 TYPE_BARS statusBar;
552 float d = (_TargetRTProps[TargetD] > ForageCareBeginZone.get()) ? _TargetRTProps[TargetD] : 0;
553 if ( d > CommonRiskThreshold )
554 d = CommonRiskThreshold;
555 float e = (_TargetRTProps[TargetE] > ForageCareBeginZone.get()) ? _TargetRTProps[TargetE] : 0;
556 if ( e > CommonRiskThreshold )
557 e = CommonRiskThreshold;
558 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))
559 if ( _N != 0 )
560 statusBar = statusBar | ((uint32(_N)+1) << 7); // round off to next integer (TODO: ratio qtty/initial_qtty)
561 statusBar = statusBar | (uint32((127.0f-d)/**127.0f*/) << 14);
562 if (!_SafeSource) // If source is safe let 0 (client will display it full and with a special color)
563 statusBar = statusBar | (uint32((127.0f-e)/**127.0f*/) << 21);
564 statusBar = statusBar | (((uint32)(_IsExtractionInProgress)) << 28);
565 //statusBar = statusBar | (uint32(_C/**127.0f*/) << 28);
566 CMirrorPropValue<TYPE_BARS> statusBarProp( TheDataset, _DataSetRow, DSPropertyBARS );
567 statusBarProp = statusBar;
569 /*if ( (_N < 0) || (_N > 127) )
570 nlwarning( "FG: N = %g", _N );
571 if ( (_D < 0) || (_D > 127) )
572 nlwarning( "FG: N = %g", _D );
573 if ( (_E < 0) || (_E > 127) )
574 nlwarning( "FG: N = %g", _E );*/
576 // Set kami angry level information & extra time state (store in orientation!)
577 float angryLevel7 = 127.0f - (127.0f * _DepositForK->kamiAnger() / ForageKamiAngerThreshold2.get());
578 uint extraTime7 = _ExtraExtractionTime * 127 / _ExtractionTime;
579 uint inclBonusExtraTime7 = _IncludedBonusExtractionTime * 127 / _ExtractionTime;
581 CMirrorPropValue<TYPE_ORIENTATION> angryLevelExtraTimeProp( TheDataset, _DataSetRow, DSPropertyORIENTATION );
582 TYPE_ORIENTATION angryLevelExtraTimePropV = angryLevel7;
583 if ( _IsExtractionInProgress )
584 angryLevelExtraTimePropV += (( ((float)inclBonusExtraTime7) * 128.0f + (float)extraTime7) * 128.0f);
585 angryLevelExtraTimeProp = angryLevelExtraTimePropV; // stored as float, should be converted to 21b uint by FS
587 // Reset explosion visual fx if needed
588 if ( _ExplosionResetCounter == 0 )
590 CMirrorPropValue<TYPE_VISUAL_FX> visualFx( TheDataset, _DataSetRow, DSPropertyVISUAL_FX );
591 visualFx = (visualFx() & 0x3FF); // unset bit 10
592 _ExplosionResetCounter = -1;
594 else if ( _ExplosionResetCounter > 0 )
596 --_ExplosionResetCounter;
604 void CHarvestSource::sendMessageToExtractors( const char *msg )
606 H_AUTO(CHarvestSource_sendMessageToExtractors);
608 CForagers::const_iterator it;
609 for ( it=_Foragers.begin(); it!=_Foragers.end(); ++it )
611 const TDataSetRow& extRowId = (*it);
612 PHRASE_UTILITIES::sendDynamicSystemMessage( extRowId, msg );
620 void CHarvestSource::sendMessageToExtractors( const char *msg, sint32 param )
622 H_AUTO(CHarvestSource_sendMessageToExtractors);
624 CForagers::const_iterator it;
625 for ( it=_Foragers.begin(); it!=_Foragers.end(); ++it )
627 const TDataSetRow& extRowId = (*it);
628 SM_STATIC_PARAMS_1(params, STRING_MANAGER::integer);
629 params[0].Int = param;
630 PHRASE_UTILITIES::sendDynamicSystemMessage( extRowId, msg, params );
635 CVariable<sint32> ForageForceImpactScheme( "egs", "ForageForceImpactScheme", "", -1 );
638 * Begin an extraction action. Once the extraction process is started, the source remains in
639 * extraction mode until the extraction time is elapsed (even if players stop/restart
640 * extracting).
641 * Return true if the forager is the extractor (the first one on the source)
643 bool CHarvestSource::beginExtraction( const TDataSetRow& forager, bool isNewbie )
645 H_AUTO(CHarvestSource_beginExtraction);
647 bool foragerIsFirstExtractor = false;
648 if ( ! _IsExtractionInProgress )
650 _IsExtractionInProgress = true;
651 foragerIsFirstExtractor = true;
652 _IsInNewbieMode = isNewbie; // only the first one sets the mode
654 // Tell him the max quality
655 SM_STATIC_PARAMS_1(params, STRING_MANAGER::integer);
656 params[0].Int = (sint32)_MaxQuality;
657 PHRASE_UTILITIES::sendDynamicSystemMessage( forager, "FORAGE_SOURCE_MAXLEVEL", params );
659 // Set the impact scheme
660 setNewImpactScheme(); // at this time, _Foragers is empty so nobody is told yet
663 // Add player to extractor to list (if new)
664 CForagers::iterator it = find( _Foragers.begin(), _Foragers.end(), forager );
665 if ( it == _Foragers.end() )
667 _Foragers.push_back( forager );
669 // Tell him the impact mode
670 SM_STATIC_PARAMS_1(params, STRING_MANAGER::integer);
671 params[0].Int = (sint32)_IImpactMappingScheme;
672 PHRASE_UTILITIES::sendDynamicSystemMessage( forager, "FORAGE_SOURCE_IMPACT_MODE", params );
674 else if ( it == _Foragers.begin() )
676 foragerIsFirstExtractor = true;
679 return foragerIsFirstExtractor;
684 * Set a new mapping scheme of property impact
686 void CHarvestSource::setNewImpactScheme()
688 H_AUTO(CHarvestSource_setNewImpactScheme);
690 // Set mapping scheme of property impact
691 if ( _IsInNewbieMode )
693 // Force "low dangers" for 1 newbie extractor
694 _IImpactMappingScheme = (uint16)LowDangerMappings[RandomGenerator.rand( 1 )];
696 else
698 // Normal dangers
699 if ( ForageForceImpactScheme.get() == -1 )
700 _IImpactMappingScheme = (uint16)RandomGenerator.rand( 5 );
701 else
702 _IImpactMappingScheme = (uint16)ForageForceImpactScheme.get();
705 sendMessageToExtractors( "FORAGE_SOURCE_IMPACT_MODE", (sint32)_IImpactMappingScheme );
707 #ifdef NL_DEBUG
708 nldebug( "FG: map scheme: %u", _IImpactMappingScheme );
709 #endif
713 bool ForceDropProp = false;
714 NLMISC_VARIABLE( bool, ForceDropProp, "" );
716 CVariable<float> ForceAbsorption( "egs", "ForceAbsorption", "", 0 );
720 * Update the source state with an extraction (see doc in .h).
722 void CHarvestSource::extractMaterial( float *reqPosProps, float *absPosProps, float qualityCeilingFactor, float qualitySlowFactor, float *results, float successFactor, uint8 lifeAbsorberRatio, const TDataSetRow& extractingEntityRow, CHarvestSource::TRealTimeProp& propDrop )
724 H_AUTO(CHarvestSource_extractMaterial);
726 CCharacter* player = PlayerManager.getChar( extractingEntityRow );
728 ++_NbExtractions; // it's 1 at the first call, so that the initial value is used
729 float nbe = (float)_NbExtractions;
731 if ( (successFactor < 0.1f) || ForceDropProp )
733 if ( _NbExtractions < 6 )
734 propDrop = Q; // don't drop A at the beginning (wait to reach 1 (cumulated) would be better)
735 else
736 propDrop = (TRealTimeProp)(RandomGenerator.rand(1)+1);
737 nldebug( "Prop drop %u", propDrop );
740 // Aperture: converges towards the requested value, except when a drop occurs (0 at this step)
741 // if ( reqPosProps[A] > 0 )
743 float obtainedQuantity = (propDrop == A) ? 0.0f : (results[A]*ForageQuantitySlowFactor.get() + reqPosProps[A]) / (ForageQuantitySlowFactor.get()+1);
745 // Apply possible aperture bonus
746 if ( _BonusForAPct != 0 )
748 obtainedQuantity += obtainedQuantity * (float)((uint)_BonusForAPct) * 0.01f;
751 // Extract material
752 if ( _ForageSite )
753 obtainedQuantity = _ForageSite->consume( obtainedQuantity ); // consume (and limit) in forage site & deposit
754 if ( obtainedQuantity > _N ) // should not occur because _N uses the deposit quantity
755 obtainedQuantity = _N;
756 _N -= obtainedQuantity;
757 _A = (_A*nbe + obtainedQuantity) / (nbe + 1.0f); // average per source
758 _DepositForK->incKamiAnger( obtainedQuantity, _Foragers );
759 if ( (obtainedQuantity == 0) && (results[A] != 0) && (! (propDrop == A)) )
760 PHRASE_UTILITIES::sendDynamicSystemMessage( _Foragers[0], "FORAGE_DEPOSIT_IS_EMPTY" );
761 results[A] = obtainedQuantity;
763 // add spire effect ( quantity )
764 if ( player )
766 const CSEffect* pEffect = player->lookForActiveEffect( EFFECT_FAMILIES::TotemHarvestQty );
767 if ( pEffect != NULL )
769 results[A] *= ( 1.0f + pEffect->getParamValue() / 100.0f );
772 // add item special effect
773 if ( player )
775 std::vector<SItemSpecialEffect> effects = player->lookForSpecialItemEffects(ITEM_SPECIAL_EFFECT::ISE_FORAGE_ADD_RM);
776 std::vector<SItemSpecialEffect>::const_iterator it, itEnd;
777 double addedQty = 0.;
778 for (it=effects.begin(), itEnd=effects.end(); it!=itEnd; ++it)
780 float rnd = RandomGenerator.frand();
781 if (rnd<it->EffectArgFloat[0])
783 addedQty += it->EffectArgFloat[1];
784 PHRASE_UTILITIES::sendItemSpecialEffectProcMessage(ITEM_SPECIAL_EFFECT::ISE_FORAGE_ADD_RM, player, NULL, (sint32)(it->EffectArgFloat[1]*100.));
787 results[A] *= 1.0f + (float)addedQty;
790 // else
791 // {
792 // results[A] = 0;
793 // }
795 // Speed: always the requested speed (otherwise, looks like a bug for the player when it's the speed of the action)
796 results[S] = reqPosProps[S];
797 _S = (_S*nbe + results[S]) / (nbe + 1.0f); // average per source
799 // Quality: converges towards the requested value, except when a drop occurs (0 at this step)
800 float usedReqQ = (propDrop == Q) ? 0.0f : reqPosProps[Q] * qualityCeilingFactor;
801 float resQ = (results[Q]*qualitySlowFactor + usedReqQ) / (qualitySlowFactor+1);
802 float maxQOfSource = (float)_MaxQuality;
803 if ( resQ > maxQOfSource )
805 resQ = maxQOfSource;
806 //if ( results[Q] < (float)_MaxQuality )
807 // nldebug( "Quality limited by source to %hu", _MaxQuality ); // TODO: tell the player(s)
809 if ( (resQ < _Q) || (resQ < reqPosProps[Q]) || (! ForageQualityCeilingClamp.get()) )
811 // Set Q only if not increasing and exceeding requested quality
812 results[Q] = resQ;
814 else
816 // Clamp Q to the max required by the player
817 results[Q] = reqPosProps[Q];
819 float prevQ = _Q;
820 _Q = results[Q]; // now there is only one extractor => the Q of the source is the resulting Q
821 if ( ((prevQ < reqPosProps[Q]) && (_Q == reqPosProps[Q])) || ((prevQ < maxQOfSource) && (_Q == maxQOfSource)) )
823 setNewImpactScheme(); // we just reached the max quality
826 // Calc impact of the new average values
828 // Previously, the impact depended on the level of the extraction:
829 // float quantityBaseImpact = _A * _S * ForageQuantityImpactFactor.get();
830 // float qualityBaseImpact = (_Q - oldQ) * ForageQualityImpactFactor.get();
832 // Now it's constant (=max), but the amount of damage depends on the level of the extraction:
833 float quantityBaseImpact = MaxAS * ForageQuantityImpactFactor.get();
834 float qualityBaseImpact = MaxDeltaQ * ForageQualityImpactFactor.get();
835 uint impactScheme = _IImpactMappingScheme;
836 if ( impactScheme >= SpecialNewbieImpactSchemeD)
838 // Lower impacts for newbies
839 impactScheme -= SpecialNewbieImpactSchemeD;
840 quantityBaseImpact *= 0.5f;
841 qualityBaseImpact *= 0.5f;
843 if ( ForceAbsorption.get() != 0 )
845 absPosProps[A] = ForceAbsorption.get();
846 absPosProps[Q] = ForceAbsorption.get();
847 absPosProps[S] = ForceAbsorption.get();
849 for ( uint i=D; i!=NbRTProps; ++i )
851 if (i==E && _SafeSource)
852 break;
853 uint impactType = ImpactSchemes[impactScheme][i-NbPosRTProps];
854 float impact;
855 switch ( impactType )
857 case 0 : impact = quantityBaseImpact * (1.0f - absPosProps[A]); break;
858 case 1 : impact = qualityBaseImpact * (1.0f - absPosProps[Q]); break;
859 default: impact = (quantityBaseImpact + qualityBaseImpact) / 2.0f * (1.0f - absPosProps[S]); break; // bound on the average of both, absorption of S
862 impact += RandomGenerator.frandPlusMinus( impact ); // result impact from 0 to impact*2
864 // add spire effect ( aggressivity )
865 if ( player )
867 const CSEffect* pEffect = player->lookForActiveEffect( EFFECT_FAMILIES::TotemHarvestAgg );
868 if ( pEffect != NULL )
870 impact *= ( 1.0f - pEffect->getParamValue() / 100.0f );
874 if ( impact < 0 ) impact = 0; // impact can't be negative
875 if ( (i==D) && (lifeAbsorberRatio != 0) )
877 // Damage the life absorber, instead of impacting D
878 CEntityBase *entity = CEntityBaseManager::getEntityBasePtr( extractingEntityRow ); // getEntityBasePtr() tests TheDataset.isAccessible( extractingEntity )
879 if ( entity )
881 float impactOnHP = ((float)lifeAbsorberRatio) * impact * 0.01f;
882 impact -= impactOnHP;
883 float dmgRatio = impactOnHP * ForageHPRatioPerSourceLifeImpact.get();
884 sint32 dmg = (sint32)((float)entity->maxHp() * dmgRatio);
885 if ( dmg != 0 )
886 CHarvestSource::hitEntity( RYZOMID::forageSource, entity, dmg, dmg, true );
889 if ( (_TargetRTProps[i-D] < CommonRiskThreshold*0.90f) && (_TargetRTProps[i-D] + impact > CommonRiskThreshold) )
891 // Avoid a brutal unnatural end, make a step just before reaching threshold
892 impactRTProp( (TTargetRTProp)(i-D), CommonRiskThreshold - 2.0f );
894 else
896 // Normal impact
897 impactRTProp( (TTargetRTProp)(i-D), _TargetRTProps[i-D] + impact );
904 * Update the source state with a care (see doc in .h)
906 void CHarvestSource::takeCare( float *deltas, bool *isUseful )
908 H_AUTO(CHarvestSource_takeCare);
910 // Do not count care if from the "begin zone"
911 if ( _TargetRTProps[TargetD] > ForageCareBeginZone.get() )
912 *isUseful = true;
913 if ( _TargetRTProps[TargetE] > ForageCareBeginZone.get() )
914 *isUseful = true;
915 //if ( deltas[DeltaD] > ForageCareBeginZone.get() )
916 // *isUseful = true;
918 // Calc actual deltas
919 deltas[DeltaD] += RandomGenerator.frandPlusMinus( deltas[DeltaD]*0.05f );
920 deltas[DeltaE] += RandomGenerator.frandPlusMinus( deltas[DeltaE]*0.05f );
921 //deltas[DeltaC] += RandomGenerator.frandPlusMinus( deltas[DeltaC]*0.05f );
923 // TODO: impact on S,A,Q
925 // Apply deltas
926 float targetValue = _TargetRTProps[TargetD] - (deltas[DeltaD] * ForageCareFactor.get());
927 if ( targetValue < 0 )
928 targetValue = 0;
929 impactRTProp( TargetD, targetValue );
930 if (!_SafeSource)
932 targetValue = _TargetRTProps[TargetE] - (deltas[DeltaE] * ForageCareFactor.get());
933 if ( targetValue < 0 )
934 targetValue = 0;
935 impactRTProp( TargetE, targetValue );
937 //_C -= (deltas[DeltaC] * ForageCareFactor.get());
938 //if ( _C < 0 ) _C = 0;
943 * When the threshold of E is reached.
945 void CHarvestSource::makeDamagingEvent()
947 H_AUTO(CHarvestSource_makeDamagingEvent);
949 sint32 r = RandomGenerator.rand( 1 );
950 if ( r == 0 )
952 spawnToxicCloud();
954 else
956 explode();
959 _Events.clear();
960 setNewImpactScheme();
967 class CForageDamagingEventRangeSelector : public CRangeSelector
969 public:
970 void buildTargetList( sint32 x, sint32 y, float radius /*, float minFactor*/ )
972 H_AUTO(CForageDamagingEventRangeSelector_buildTargetList);
974 buildDisc( 0, x, y, radius, EntityMatrix, true );
977 float getFactor( uint entityIdx )
979 return 1.0f; // every entity in the radius gets the same damage, not depending of his location
980 } // improvement note: for explosion, could be decreasing with the distance
985 * A continuous damaging event
987 void CHarvestSource::spawnToxicCloud()
989 H_AUTO(CHarvestSource_spawnToxicCloud);
991 sendMessageToExtractors( "SOURCE_TOXIC_CLOUD" );
993 // Get random cloud params near the source (TODO: Z)
994 float dmgFactor = getDamageFactor();
995 sint32 iRadius = min( (sint32)2, (sint32)(dmgFactor * 3.0f) ); // => mapping [0..1] to {0, 1, 2}
996 float Radiuses [3] = { 1.5f, 3.0f, 5.0f }; // corresponding to the 3 sheets
997 float radius = Radiuses[iRadius];
998 CVector cloudPos( _Pos.x, _Pos.y, 0.0f );
999 if ( iRadius != 0 )
1001 // For a big toxic cloud, shift the centre in a axis-aligned square of 4 m width (max dist = 2.8 m)
1002 const float MaxAxisDistFromSource = 2.0f;
1003 float dX = RandomGenerator.frand( MaxAxisDistFromSource*2.0f );
1004 float dY = RandomGenerator.frand( MaxAxisDistFromSource*2.0f );
1005 cloudPos.x += dX - MaxAxisDistFromSource;
1006 cloudPos.y += dY - MaxAxisDistFromSource;
1009 // Spawn the toxic cloud
1010 CToxicCloud *tc = new CToxicCloud();
1011 tc->init( cloudPos, radius, (sint32)(dmgFactor * ToxicCloudDamage.get()), ToxicCloudUpdateFrequency );
1013 CSheetId sheet( toString( "toxic_cloud_%d.fx", iRadius ));
1014 if ( tc->spawn( sheet ) )
1016 CEnvironmentalEffectManager::getInstance()->addEntity( tc );
1017 #ifdef NL_DEBUG
1018 nldebug( "FG: Toxic cloud spawned (radius %g)", radius );
1019 #endif
1021 else
1023 nlwarning( "FG: Unable to spawn toxic cloud (mirror range full?)" );
1024 delete tc;
1030 * Test which entities to hit (all entities except NPC and non-pets creatures)
1032 inline bool isAffectedByForageDamage( CEntityBase *entity )
1034 uint8 entityType = entity->getId().getType();
1035 return ! ((entityType == RYZOMID::npc) ||
1036 ((entityType == RYZOMID::creature) &&
1037 (entity->getRace() != EGSPD::CPeople::MektoubMount) &&
1038 (entity->getRace() != EGSPD::CPeople::MektoubPacker)));
1043 * A one-time damaging event
1045 void CHarvestSource::explode()
1047 H_AUTO(CHarvestSource_explode);
1049 sendMessageToExtractors( "SOURCE_EXPLOSION" );
1051 // Set the FX
1052 CMirrorPropValue<TYPE_VISUAL_FX> visualFx( TheDataset, _DataSetRow, DSPropertyVISUAL_FX );
1053 visualFx = (visualFx() | 0x400); // set bit 10
1054 _ExplosionResetCounter = ExplosionResetPeriod;
1056 // Get entities around the source, and hit them
1057 if ( HarvestAreaEffectOn )
1059 // Calculate damage
1060 float fDmg = getDamageFactor() * ForageExplosionDamage.get();
1061 float dmgAvoided = fDmg;
1062 for ( CRDEvents::const_iterator iev=_Events.begin(); iev!=_Events.end(); ++iev )
1064 const CReduceDamageEvent& event = (*iev);
1065 if ( CTickEventHandler::getGameCycle() - event.Time < ForageReduceDamageTimeWindow.get() )
1067 //nldebug( "Damage %.1f x %.1f", fDmg, event.Ratio );
1068 fDmg *= event.Ratio; // multiple events are multiplied (e.g. 50% and 50% again do 25%)
1071 dmgAvoided = dmgAvoided - fDmg;
1072 bool wereAllEventsMissed = (! _Events.empty()) && (dmgAvoided == 0);
1074 // Make area of effect
1075 const float explosionRadius = 4.0f;
1076 CForageDamagingEventRangeSelector targetSelector;
1077 targetSelector.buildTargetList( (sint32)(_Pos.x * 1000.0f), (sint32)(_Pos.y * 1000.0f), explosionRadius );
1078 const vector<CEntityBase*>& targets = targetSelector.getEntities();
1079 for ( vector<CEntityBase*>::const_iterator it=targets.begin(); it!=targets.end(); ++it )
1081 CEntityBase *entity = (*it);
1082 if ( entity && isAffectedByForageDamage( entity ) )
1084 sint32 dmg = (sint32)(entity->getActualDamageFromExplosionWithArmor( fDmg ));
1085 CHarvestSource::hitEntity( RYZOMID::forageSource, entity, dmg, (sint32)(fDmg+dmgAvoided), false, (sint32)dmgAvoided ); // is not blocked by any armor
1087 if ( wereAllEventsMissed && (entity->getId().getType() == RYZOMID::player) )
1089 PHRASE_UTILITIES::sendDynamicSystemMessage( entity->getEntityRowId(), "SOURCE_DMG_REDUX_MISSED" );
1098 * Reduce the damage of the next blowing up (if it is in near time delta)
1100 void CHarvestSource::reduceBlowingUpDmg( float ratio )
1102 H_AUTO(CHarvestSource_reduceBlowingUpDmg);
1104 CReduceDamageEvent event;
1105 event.Time = CTickEventHandler::getGameCycle();
1106 event.Ratio = ratio;
1107 _Events.push_back( event );
1112 * Get the damage factor of a source (for explosion or toxic cloud)
1114 float CHarvestSource::getDamageFactor() const
1116 H_AUTO(CHarvestSource_getDamageFactor);
1118 // Map linearly using 5 -> 0.5, 80 -> 1.0 (to match previous algorithm)
1119 float statQualityFactor = ((((float)getStatQuality())+70.0f) / 150.0f);
1120 switch ( ImpactSchemes[_IImpactMappingScheme][E] )
1122 case 0 : return _A * _S * statQualityFactor / MaxAS; break;
1123 case 1 : return _Q * statQualityFactor / MaxQ; break; // not using DeltaQ but Q
1124 default: return (_A*_S/MaxAS + _Q/MaxQ) * statQualityFactor / 2; break;
1130 * Return the stat quality of the raw material
1132 /*CHarvestSource::TStatClassForage CHarvestSource::getStatQuality() const
1134 H_AUTO(CHarvestSource_getStatQuality);
1136 const CStaticItem *itemInfo = CSheets::getForm( _MaterialSheet );
1137 if ( itemInfo && itemInfo->Mp )
1139 // (0..20 - 1) / 20 = 0
1140 // (21..40 - 1) / 20 = 1
1141 // (41..51 - 1) / 20 = 2;
1142 // (52..59) / 20 = 2
1143 // (61..79) / 20 = 3
1144 // (80..99) / 20 = 4
1145 // (100...) -> 4
1146 sint16 statEnergy = (sint16)(itemInfo->Mp->StatEnergy);
1147 TStatClassForage sq =
1148 (statEnergy < 51) ? (statEnergy - 1) / 20 :
1149 ((statEnergy < 100) ? statEnergy / 20 : SupremeMagnificient);
1150 return sq;
1152 nlwarning( "Invalid raw material %s", _MaterialSheet.toString().c_str() );
1153 return BasicPlainAverage;
1158 * Return the stat quality of the raw material [0..100]
1159 * (Frequent values: 20 35 50 65 80)
1161 uint16 CHarvestSource::getStatQuality() const
1163 H_AUTO(CHarvestSource_getStatQuality);
1165 const CStaticItem *itemInfo = CSheets::getForm( _MaterialSheet );
1166 if ( itemInfo && itemInfo->Mp )
1168 return itemInfo->Mp->StatEnergy;
1170 nlwarning( "Invalid raw material %s", _MaterialSheet.toString().c_str() );
1171 return 0;
1176 * Damage an entity
1178 void CHarvestSource::hitEntity( RYZOMID::TTypeId aggressorType, CEntityBase *entity, sint32 hpDamageAmount, sint32 hpDamageAmountWithoutArmour, bool isIntentional, sint32 hpAvoided )
1180 H_AUTO(CHarvestSource_hitEntity);
1182 if ( entity->isDead())
1183 return;
1185 bool killed = entity->changeCurrentHp( -hpDamageAmount );
1186 if ( isIntentional )
1188 SM_STATIC_PARAMS_1(params, STRING_MANAGER::integer);
1189 params[0].Int = hpDamageAmount;
1190 PHRASE_UTILITIES::sendDynamicSystemMessage( entity->getEntityRowId(), "FORAGE_ABSORB_DMG", params );
1192 else
1193 PHRASE_UTILITIES::sendNaturalEventHitMessages( aggressorType, entity->getEntityRowId(), hpDamageAmount, hpDamageAmountWithoutArmour, hpAvoided );
1194 if ( killed )
1195 PHRASE_UTILITIES::sendDeathMessages( TDataSetRow(), entity->getEntityRowId() );
1199 NLMISC_VARIABLE( sint32, ForageSourceDisplay, "Row index of source to verbose" );
1203 * Testing
1206 TDataSetRow TestSourceRow;
1208 void forageTestDoBegin()
1210 CHarvestSource templateSource, *testSource;
1211 templateSource.setLifetime( 1140 );
1212 templateSource.setProspectionExtraExtractionTime( 1140 );
1213 CStaticDepositRawMaterial rm;
1215 testSource = new CHarvestSource;
1216 CDeposit deposit;
1217 testSource->init( templateSource, CVector2f(1000.0f,1000.0f), NULL, &deposit, &rm, 1.0f );
1218 TDataSetRow dsr;
1219 if ( testSource->spawnBegin( 0, dsr, false ) )
1221 TestSourceRow = testSource->rowId();
1223 else
1225 delete testSource;
1229 bool forageTestDoExtract(
1230 NLMISC::CLog& log,
1231 uint nbIterations,
1232 float reqPeriod,
1233 float reqA,
1234 float reqQ,
1235 float absorption,
1236 float successFactor )
1238 CHarvestSource *testSource = CHarvestSourceManager::getInstance()->getEntity( TestSourceRow );
1239 if ( ! testSource )
1241 log.displayNL( "Call forageTestBegin first" );
1242 return true;
1245 // Request and output results
1246 FILE *f = nlfopen(getLogDirectory() + "forage_test.csv", "at" );
1247 FILE *f2 = nlfopen(getLogDirectory() + "forage_test.log", "at" );
1248 float reqS = 1.0f / (reqPeriod * 10.0f);
1249 float req [CHarvestSource::NbPosRTProps];
1250 float abs [CHarvestSource::NbPosRTProps];
1251 float res [CHarvestSource::NbPosRTProps];
1252 static bool FirstTime = true;
1253 req[CHarvestSource::S] = reqS;
1254 req[CHarvestSource::A] = reqA;
1255 req[CHarvestSource::Q] = reqQ;
1256 abs[CHarvestSource::S] = absorption;
1257 abs[CHarvestSource::A] = absorption;
1258 abs[CHarvestSource::Q] = absorption;
1259 res[CHarvestSource::S] = 0.025f;
1260 res[CHarvestSource::A] = 0.0f;
1261 res[CHarvestSource::Q] = 0.0f;
1262 if ( FirstTime )
1264 FirstTime = false;
1265 fprintf( f, "A;Q;D;E;C;reqS;reqA;reqQ;qty;scheme;limit;\n" );
1267 testSource->beginExtraction( TDataSetRow::createFromRawIndex( INVALID_DATASET_INDEX ), false );
1268 bool eventD = false, eventE = false, eventC = false;
1269 for ( uint i=0; i!=nbIterations; ++i )
1271 TDataSetRow row;
1272 CHarvestSource::TRealTimeProp propDrop;
1273 testSource->extractMaterial( req, abs, ForageQualityCeilingFactor.get(), ForageQualitySlowFactor.get(), res, successFactor, 0, row, propDrop );
1274 fprintf( f, "%g;%g;%g;%g;%g;%g;%g;%g;%g;%u;%u;\n",
1275 res[CHarvestSource::A], res[CHarvestSource::Q],
1276 testSource->getD(), testSource->getE(), 0.f /*testSource->getC()*/,
1277 reqS, reqA, reqQ,
1278 testSource->quantity(), testSource->getImpactScheme()*5, 127 );
1279 if ( (!eventD) && (testSource->getD() > 127) )
1281 fprintf( f2, "D: %u\n", i );
1282 eventD = true;
1284 if ( (!eventE) && (testSource->getE() > 127) )
1286 fprintf( f2, "E: %u\n", i );
1287 eventE = true;
1289 /*if ( (!eventC) && (testSource->getC() > 127) )
1291 fprintf( f2, "C: %u\n", i );
1292 eventC = true;
1295 if ( !eventD )
1296 fprintf( f2, "D---\n" );
1297 if ( !eventE )
1298 fprintf( f2, "E---\n" );
1299 if ( !eventC )
1300 fprintf( f2, "C---\n" );
1301 fclose( f );
1302 fclose( f2 );
1304 return true;
1307 void forageTestDoEnd()
1309 CHarvestSource *testSource = CHarvestSourceManager::getInstance()->getEntity( TestSourceRow );
1310 if ( ! testSource )
1311 return;
1313 CHarvestSourceManager::getInstance()->destroyEntity( testSource->rowId() );
1314 testSource = NULL;
1317 NLMISC_COMMAND( forageTestBegin, "Start forage test", "" )
1319 forageTestDoBegin();
1320 return true;
1323 NLMISC_COMMAND( forageTestExtract, "Make a test extraction (floats in percent)",
1324 "<nbIterations> <reqPeriod=2> <reqA=2> <reqQ=50> <absorption=10> <successFactor=100>" )
1326 // Read args
1327 sint n = (sint)args.size();
1328 uint nbIterations = 1;
1329 float reqPeriod = 2.0f;
1330 float reqA = 2.0f;
1331 float reqQ = 50.0f;
1332 float absorption = 0.1f;
1333 float successFactor = 1.0f;
1334 if ( n > 0 )
1336 NLMISC::fromString(args[0], nbIterations);
1337 if ( n > 1 )
1339 NLMISC::fromString(args[1], reqPeriod);
1340 if ( n > 2)
1342 NLMISC::fromString(args[2], reqA);
1343 if ( n > 3 )
1345 NLMISC::fromString(args[3], reqQ);
1346 if ( n > 4)
1348 NLMISC::fromString(args[4], absorption);
1349 absorption /= 100.0f;
1350 if ( n > 5 )
1352 NLMISC::fromString(args[5], successFactor);
1353 successFactor /= 100.0f;
1361 return forageTestDoExtract( log, nbIterations, reqPeriod, reqA, reqQ, absorption, successFactor );
1364 NLMISC_COMMAND( forageTestBatch, "Batch forage tests", "" )
1366 uint nbIterations = 15;
1367 float reqPeriod;
1368 float reqA;
1369 float reqQ;
1370 float absorption;
1371 float successFactor = 1.0f;
1373 for ( absorption=0.1f; absorption<=0.8f; absorption+=0.7f )
1375 forageTestDoBegin();
1376 for ( reqQ=1.0f; reqQ<=251.0f; reqQ+=50.0f )
1378 for ( reqA=1.0f; reqA<=5.0f; reqA+=2.0f )
1380 for ( reqPeriod=2.2f; reqPeriod>=0.2f; reqPeriod-=0.5f )
1382 forageTestDoExtract( log, nbIterations, reqPeriod, reqA, reqQ, absorption, successFactor );
1383 CHarvestSource *testSource = CHarvestSourceManager::getInstance()->getEntity( TestSourceRow );
1384 if ( testSource )
1386 CHarvestSource templateSource;
1387 templateSource.setLifetime( 1140 );
1388 templateSource.setProspectionExtraExtractionTime( 1140 );
1389 testSource->resetSource( templateSource );
1390 testSource->setN( 120 );
1391 testSource->setD( 0 );
1392 testSource->setE( 0 );
1393 //testSource->setC( 0 );
1398 forageTestDoEnd();
1401 return true;
1404 NLMISC_COMMAND( forageTestEnd, "End forage test", "" )
1406 // TODO: despawn spawned source!
1407 forageTestDoEnd();
1408 return true;
1413 NLMISC_DYNVARIABLE( uint, NbForageSources, "Number of forage sources" )
1415 if ( get )
1416 *pointer = CHarvestSourceManager::getInstance()->nbEntities();
1419 NLMISC_VARIABLE( uint, NbAutoSpawnedForageSources, "Number of auto-spawned forage sources" );