1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
24 #include "combat_phrase.h"
26 #include "nel/net/message.h"
28 #include "nel/misc/common.h"
30 #include "game_share/msg_combat_move_service.h"
31 #include "game_share/mode_and_behaviour.h"
32 #include "game_share/combat_state.h"
33 //#include "game_share/sheath.h"
34 #include "game_share/entity_structure/statistic.h"
36 #include "phrase_utilities_functions.h"
37 #include "entity_base.h"
38 #include "character.h"
39 #include "egs_mirror.h"
40 #include "phrase_manager.h"
41 #include "player_manager.h"
42 #include "s_phrase_factory.h"
44 #include "combat_action_stun.h"
50 using namespace NLNET
;
51 using namespace NLMISC
;
52 using namespace RY_GAME_SHARE
;
58 extern CRandom RandomGenerator
;
59 extern CPhraseManager
*PhraseManager
;
60 extern CPlayerManager PlayerManager
;
63 uint16 HandToHandDamage
= 20*260/11;
64 uint16 HandToHandSpeed
= 30; // 3s = 30 ticks
68 DEFAULT_SPHRASE_FACTORY( CCombatPhrase
, BRICK_TYPE::COMBAT
);
83 bool CCombatPhrase::build( const TDataSetRow
& actorRowId
, const std::vector
< const CStaticBrick
* >& bricks
)
85 if (actorRowId
.isValid() && TheDataset
.isDataSetRowStillValid(actorRowId
) )
87 // create attacker structure
88 CEntityBase
*attacker
= PHRASE_UTILITIES::entityPtrFromId(actorRowId
);
92 if (attacker
->getId().getType() == RYZOMID::player
)
94 _Attacker
= new CCombatAttackerPlayer(actorRowId
);
100 _Attacker
= new CCombatAttackerAI(actorRowId
);
106 // create defender structure -> wait first validation
109 for (uint i
= 0; i
< bricks
.size(); i
++)
111 addBrick( *bricks
[i
] );
119 //--------------------------------------------------------------
121 //--------------------------------------------------------------
122 CCombatPhrase::~CCombatPhrase()
124 if (_Attacker
!= NULL
)
126 if (_Defender
!= NULL
)
129 const uint size
= _CombatActions
.size();
130 for (uint i
= 0 ; i
< size
; ++i
)
132 if (_CombatActions
[i
] != NULL
)
133 delete _CombatActions
[i
];
138 //--------------------------------------------------------------
140 //--------------------------------------------------------------
141 CCombatPhrase::CCombatPhrase(const CStaticBrick
&rootBrick
)
144 //_RootSkill = rootBrick.Skill;
149 //--------------------------------------------------------------
151 //--------------------------------------------------------------
152 void CCombatPhrase::init()
154 _State
= CSPhrase::New
;
155 _ExecutionBehaviour
= MBEHAV::UNKNOWN_BEHAVIOUR
;
156 _SuccessBehaviour
= MBEHAV::UNKNOWN_BEHAVIOUR
;
157 _CriticalSuccessBehaviour
= MBEHAV::UNKNOWN_BEHAVIOUR
;
158 _FailureBehaviour
= MBEHAV::UNKNOWN_BEHAVIOUR
;
159 _FumbleBehaviour
= MBEHAV::UNKNOWN_BEHAVIOUR
;
160 _EndBehaviour
= MBEHAV::UNKNOWN_BEHAVIOUR
;
161 _StopBehaviour
= MBEHAV::UNKNOWN_BEHAVIOUR
;
163 _ForcedLocalisation
= SLOT_EQUIPMENT::UNDEFINED
;
168 _RootSkill
= SKILLS::unknown
;
169 _AttackSkill
= SKILLS::unknown
;
171 _CyclicPhrase
= false;
173 _TargetTooFarMsg
= false;
174 _NotEnoughStaminaMsg
= false;
175 _NotEnoughHpMsg
= false;
176 _CurrentTargetIsValid
= false;
179 _ExecutionEndDate
= 0;
181 _NbWaitingRequests
= 0;
189 _AttackSkillModifier
= 0;
190 _ExecutionLengthModifier
= 0;
191 _HitRateModifier
= 0;
195 _PhraseSuccessDamageFactor
= 1.0f
;
197 _LightArmorAbsorptionMultiplier
= 1.0f
;
198 _LightArmorWearMultiplier
= 1.0f
;
199 _MediumArmorAbsorptionMultiplier
= 1.0f
;
200 _MediumArmorWearMultiplier
= 1.0f
;
201 _HeavyArmorAbsorptionMultiplier
= 1.0f
;
202 _HeavyArmorWearMultiplier
= 1.0f
;
204 _LightArmorAbsorptionModifier
= 0;
205 _MediumArmorAbsorptionModifier
= 0;
206 _HeavyArmorAbsorptionModifier
= 0;
208 _AggroMultiplier
= 1.0f
;
211 _DamageFactor
= 1.0f
;
212 _StaminaLossFactor
= 0.0f
;
213 _StaminaLossModifier
= 0;
214 _SapLossFactor
= 0.0f
;
215 _SapLossModifier
= 0;
217 _DamagePointBlank
= 1.0f
;
218 _DamageShortRange
= 1.0f
;
219 _DamageMediumRange
= 1.0f
;
220 _DamageLongRange
= 1.0f
;
223 //--------------------------------------------------------------
225 //--------------------------------------------------------------
226 void CCombatPhrase::addBrick( const CStaticBrick
&brick
)
228 if ( brick
.ForcedLocalisation
!= SLOT_EQUIPMENT::UNDEFINED
)
230 if ( _ForcedLocalisation
== SLOT_EQUIPMENT::UNDEFINED
)
232 _ForcedLocalisation
= brick
.ForcedLocalisation
;
234 else if (brick
.ForcedLocalisation
!= _ForcedLocalisation
)
236 nlwarning("<CCombatPhrase::addBrick> Phrase as a forced localisation to %s, but the new brick (name %s) has localisation %s",SLOT_EQUIPMENT::toString(_ForcedLocalisation
).c_str(), brick
.Name
.c_str(), SLOT_EQUIPMENT::toString(brick
.ForcedLocalisation
).c_str());
240 brick
.SabrinaValue
< 0 ? _SabrinaCredit
+= abs(brick
.SabrinaValue
) : _SabrinaCost
+= brick
.SabrinaValue
;
244 for (i
=0 ; i
<brick
.Params
.size() ; ++i
)
246 switch(brick
.Params
[i
]->id())
248 case TBrickParam::HP
:
249 // INFOLOG("HP: %i",((CSBrickParamHp *)brick.Params[i])->Hp);
250 _HPCost
+= ((CSBrickParamHp
*)brick
.Params
[i
])->Hp
;
253 case TBrickParam::SAP
:
254 //printf("SAP: %i\n",((CSBrickParamSap *)brick.Params[i])->Sap);
257 case TBrickParam::STA
:
258 // INFOLOG("STA: %i",((CSBrickParamSta *)brick.Params[i])->Sta);
259 _StaminaCost
+= ((CSBrickParamSta
*)brick
.Params
[i
])->Sta
;
262 case TBrickParam::EXECUTION_LENGTH
:
263 // INFOLOG("EXECUTION_LENGTH: %i",((CSBrickParamExecutionLength *)brick.Params[i])->ExecutionLength);
264 _ExecutionLengthModifier
+= ((CSBrickParamExecutionLength
*)brick
.Params
[i
])->ExecutionLength
;
267 case TBrickParam::LATENCY_LENGTH
:
268 // INFOLOG("LATENCY_LENGTH: %i",((CSBrickParamLatencyLength *)brick.Params[i])->LatencyLength);
269 _HitRateModifier
+= ((CSBrickParamLatencyLength
*)brick
.Params
[i
])->LatencyLength
;
272 case TBrickParam::DMG_MOD
:
273 // INFOLOG("DMG_MOD: %i",((CSBrickParamDamageModifier *)brick.Params[i])->DamageModifier);
274 _DamageModifier
+= ((CSBrickParamDamageModifier
*)brick
.Params
[i
])->DamageModifier
;
277 case TBrickParam::DMG_MUL
:
278 // INFOLOG("DMG_MUL: %i",((CSBrickParamDamageFactor *)brick.Params[i])->DamageFactor);
279 _DamageFactor
+= ((CSBrickParamDamageFactor
*)brick
.Params
[i
])->DamageFactor
- 1.0f
;
282 case TBrickParam::AGGRO
:
283 // INFOLOG("AGGRO: factor %f mod %d",((CSBrickParamAggro *)brick.Params[i])->AggroFactor, ((CSBrickParamAggro *)brick.Params[i])->AggroModifier);
284 _AggroMultiplier
+= ((CSBrickParamAggro
*)brick
.Params
[i
])->AggroFactor
- 1.0f
;
285 _AggroModifier
+= ((CSBrickParamAggro
*)brick
.Params
[i
])->AggroModifier
;
288 case TBrickParam::STA_LOSS
:
289 // INFOLOG("STA_LOSS: %f, %u",((CSBrickParamStaLossFactor *)brick.Params[i])->StaLossFactor, ((CSBrickParamStaLossFactor *)brick.Params[i])->StaLossModifier);
290 _StaminaLossFactor
+= ((CSBrickParamStaLossFactor
*)brick
.Params
[i
])->StaLossFactor
;
291 _StaminaLossModifier
+= ((CSBrickParamStaLossFactor
*)brick
.Params
[i
])->StaLossModifier
;
294 case TBrickParam::SAP_LOSS
:
295 // INFOLOG("SAP_LOSS: %f, %u",((CSBrickParamSapLossFactor *)brick.Params[i])->SapLossFactor, ((CSBrickParamSapLossFactor *)brick.Params[i])->SapLossModifier);
296 _SapLossFactor
+= ((CSBrickParamSapLossFactor
*)brick
.Params
[i
])->SapLossFactor
;
297 _SapLossModifier
+= ((CSBrickParamSapLossFactor
*)brick
.Params
[i
])->SapLossModifier
;
300 case TBrickParam::ATT_SKILL_MOD
:
301 // INFOLOG("ATT_SKILL_MOD: %f",((CSBrickParamAttackSkillModifier *)brick.Params[i])->AttackSkillModifier);
302 _AttackSkillModifier
+= ((CSBrickParamAttackSkillModifier
*)brick
.Params
[i
])->AttackSkillModifier
;
305 case TBrickParam::AIM
:
306 // INFOLOG("AIMED SLOT: %s",((CSBrickParamAim *)brick.Params[i])->AimedSlot.c_str());
307 _ForcedLocalisation
= SLOT_EQUIPMENT::stringToSlotEquipment( ((CSBrickParamAim
*)brick
.Params
[i
])->AimedSlot
);
310 case TBrickParam::OPENING
:
311 INFOLOG("OPENING : %s",((CSBrickParamOpening
*)brick
.Params
[i
])->OpeningType
.c_str());
312 _Opening
= ((CSBrickParamOpening
*)brick
.Params
[i
])->OpeningType
;
315 case TBrickParam::BREAK_CAST
:
316 // nlinfo("TODO TODO BREAK_CAST : modifier on damage for break cast test %d",((CSBrickParamBreakCast *)brick.Params[i])->DamageModifier);
317 // $*STRUCT CSBrickParamBreakCast: public TBrickParam::CId <TBrickParam::BREAK_CAST>
318 // $*-i sint32 DamageModifier = 0 // damage modifier only for break cast test
321 case TBrickParam::RANGES
:
322 /*printf("RANGES: %f", ((CSBrickParamRanges *)brick.Params[i])->ShortRange);
323 printf(" : %f", ((CSBrickParamRanges *)brick.Params[i])->MediumRange);
324 printf(" : %f\n", ((CSBrickParamRanges *)brick.Params[i])->LongRange);
328 case TBrickParam::COMBAT_STUN
:
330 // $*STRUCT CSBrickParamCombatStun: public TBrickParam::CId <TBrickParam::COMBAT_STUN>
331 // $*-f float Duration // duration of the stun in seconds
332 // $*-f float DurationResisted // duration of the stun in seconds when resisted
333 // $*-i uint16 Power // stun power (to oppose to target resistance)
334 // nlinfo("COMBAT_STUN : duration %f, duration resisted %f, power %u",((CSBrickParamCombatStun *)brick.Params[i])->Duration, ((CSBrickParamCombatStun *)brick.Params[i])->DurationResisted, ((CSBrickParamCombatStun *)brick.Params[i])->Power);
335 NLMISC::TGameCycle duration
= NLMISC::TGameCycle( ((CSBrickParamCombatStun
*)brick
.Params
[i
])->Duration
/ CTickEventHandler::getGameTimeStep() );
336 NLMISC::TGameCycle durationResisted
= NLMISC::TGameCycle( ((CSBrickParamCombatStun
*)brick
.Params
[i
])->DurationResisted
/ CTickEventHandler::getGameTimeStep() );
337 uint16 power
= ((CSBrickParamCombatStun
*)brick
.Params
[i
])->Power
;
338 _CombatActions
.push_back( new CCombatActionStun( _Attacker
->getEntityRowId(), this, duration
, durationResisted
, power
));
342 case TBrickParam::LARMOR_MOD
:
343 // INFOLOG("LARMOR_MOD: %f, %u",((CSBrickParamLightArmorMod *)brick.Params[i])->AborptionFactor, ((CSBrickParamLightArmorMod *)brick.Params[i])->AborptionModifier);
344 _LightArmorAbsorptionMultiplier
+= ((CSBrickParamLightArmorMod
*)brick
.Params
[i
])->AborptionFactor
;
345 _LightArmorAbsorptionModifier
+= ((CSBrickParamLightArmorMod
*)brick
.Params
[i
])->AborptionModifier
;
348 case TBrickParam::MARMOR_MOD
:
349 // INFOLOG("MARMOR_MOD: %f, %u",((CSBrickParamMediumArmorMod *)brick.Params[i])->AborptionFactor, ((CSBrickParamMediumArmorMod *)brick.Params[i])->AborptionModifier);
350 _MediumArmorAbsorptionMultiplier
+= ((CSBrickParamMediumArmorMod
*)brick
.Params
[i
])->AborptionFactor
;
351 _MediumArmorAbsorptionModifier
+= ((CSBrickParamMediumArmorMod
*)brick
.Params
[i
])->AborptionModifier
;
354 case TBrickParam::HARMOR_MOD
:
355 // INFOLOG("HARMOR_MOD: %f, %u",((CSBrickParamHeavyArmorMod *)brick.Params[i])->AborptionFactor, ((CSBrickParamHeavyArmorMod *)brick.Params[i])->AborptionModifier);
356 _HeavyArmorAbsorptionMultiplier
+= ((CSBrickParamHeavyArmorMod
*)brick
.Params
[i
])->AborptionFactor
;
357 _HeavyArmorAbsorptionModifier
+= ((CSBrickParamHeavyArmorMod
*)brick
.Params
[i
])->AborptionModifier
;
367 //--------------------------------------------------------------
369 //--------------------------------------------------------------
370 bool CCombatPhrase::evaluate(CEvalReturnInfos
*msg
)
372 _State
= CSPhrase::Evaluated
;
375 _PhraseSuccessDamageFactor
= 0.0f
;
376 _AttackSkill
= SKILLS::unknown
;
378 _TargetTooFarMsg
= false;
379 _NotEnoughStaminaMsg
= false;
380 _NotEnoughHpMsg
= false;
381 _DisengageOnEnd
= false;
382 //_CurrentTargetIsValid = false;
383 //_MeleeCombat = true;
388 //--------------------------------------------------------------
390 //--------------------------------------------------------------
391 bool CCombatPhrase::validate()
393 if (_Attacker
== NULL
)
395 nlwarning("<CCombatPhrase::validate> Found NULL attacker.");
400 CEntityBase
*actingEntity
= _Attacker
->getEntity();
401 if (actingEntity
== NULL
)
403 nlwarning("<CCombatPhrase::validate> Cannot find entity ptr for acting entity %u", _Attacker
->getEntityRowId().getIndex());
404 _BeingProcessed
= false;
408 // set the attack flag to 0, will be set to 1 at the end of method if phrase is valid
409 actingEntity
->setActionFlag( RYZOMACTIONFLAGS::Attacks
, false );
411 _BeingProcessed
= true;
414 _ExecutionBehaviour
= MBEHAV::DEFAULT_ATTACK
;
418 if (_State
== Evaluated
)
420 else if (_State
== ExecutionInProgress
)
421 _State
= SecondValidated
;
423 nlwarning("Validate phrase while in state %u", _State
);
425 bool engaged
= false;
427 CEntityBase
*defender
= NULL
;
430 TDataSetRow targetRowId
= TheDataset
.getDataSetRow(actingEntity
->getTarget());
431 if ( !_Defender
|| targetRowId
!= _Defender
->getEntityRowId() )
433 createDefender(targetRowId
);
438 _CurrentTargetIsValid
= false;
439 errorCode
= "INVALID_TARGET";
443 // check the player has engaged a target if not auto engage the target
444 TDataSetRow entityRowId
= PhraseManager
->getEntityEngagedMeleeBy( _Attacker
->getEntityRowId() );
445 if (entityRowId
.isValid() && TheDataset
.isDataSetRowStillValid(entityRowId
) )
447 CEntityId entityId
= TheDataset
.getEntityId(entityRowId
);
448 if ( _Defender
->getEntityRowId() == entityRowId
)
458 defender
= _Defender
->getEntity();
459 if (defender
== NULL
)
461 _CurrentTargetIsValid
= false;
462 errorCode
= "INVALID_TARGET";
464 //nlwarning("<CCombatPhrase::validate> Cannot find entity ptr for defending entity %u", _Defender->getEntityRowId().getIndex());
465 //_BeingProcessed = false;
467 // PHRASE_UTILITIES::sendEngageFailedMessage( TheDataset.getEntityId(_Attacker->getEntityRowId()) );
474 if ( !_Opening
.empty() && !_Validated
)
476 // get combat initiated by current defender
477 CCombat
*combat
= PhraseManager
->getCombatInitiatedBy( _Defender
->getEntityRowId() );
479 COMBAT_HISTORY::TCombatHistory event
= COMBAT_HISTORY::Unknown
;
482 COMBAT_HISTORY::TCombatHistory event
= combat
->historyEvent(0);
484 if (_Opening
== string("Parry") && event
!= COMBAT_HISTORY::Parry
)
486 PHRASE_UTILITIES::sendSimpleMessage( _Attacker
->getEntityRowId(), "EGS_OPENING_PARRY_FAILED");
489 if (_Opening
== string("Dodge") && event
!= COMBAT_HISTORY::Dodge
)
491 PHRASE_UTILITIES::sendSimpleMessage( _Attacker
->getEntityRowId(), "EGS_OPENING_DODGE_FAILED");
495 PHRASE_UTILITIES::sendSimpleMessage( _Attacker
->getEntityRowId(), "EGS_OPENING_SUCCESS");
498 _CurrentTargetIsValid
= checkTargetValidity( _Defender
->getEntityRowId(), errorCode
);
502 if (!_CurrentTargetIsValid
)
505 // PHRASE_UTILITIES::sendEngageFailedMessage(TheDataset.getEntityId(_Attacker->getEntityRowId()));
507 PHRASE_UTILITIES::sendSimpleMessage(_Attacker
->getEntityRowId(), errorCode
);
511 // get the weapon used by the acting entity and check it
512 CCombatWeapon weapon
;
514 if ( _Attacker
->getItem( CCombatAttacker::RightHandItem
, weapon
) )
516 if (weapon
.Family
== ITEMFAMILY::MELEE_WEAPON
)
520 else if (weapon
.Family
== ITEMFAMILY::RANGE_WEAPON
)
522 _MeleeCombat
= false;
524 if ( _Attacker
->getItem( CCombatAttacker::Ammo
, ammo
) )
527 if ( !_Attacker
->checkAmmoAmount() )
529 PHRASE_UTILITIES::sendSimpleMessage( _Attacker
->getEntityRowId(), "EGS_NOT_ENOUGH_AMMO");
530 _BeingProcessed
= false;
535 _Attacker
->lockAmmos();
539 PHRASE_UTILITIES::sendSimpleMessage( _Attacker
->getEntityRowId(), "BS_NO_AMMO");
540 _BeingProcessed
= false;
546 DEBUGLOG("<CCombatPhrase::validate> Entity %u, item in right hand is not a weapon", _Attacker
->getEntityRowId().getIndex() );
547 // ERROR -> Not a weapon
548 PHRASE_UTILITIES::sendSimpleMessage(_Attacker
->getEntityRowId(), "BS_ITEM_INCOMPATIBLE");
550 // PHRASE_UTILITIES::sendEngageFailedMessage( TheDataset.getEntityId(_Attacker->getEntityRowId()) );
552 _BeingProcessed
= false;
558 _MeleeCombat
= true; // hand to hand
561 if ( _CurrentTargetIsValid
&& defender
!= 0 && TheDataset
.getEntityId(_Attacker
->getEntityRowId()).getType() == RYZOMID::player
)
563 // check target is still alive
564 // test the targeted entity is still alive
565 const sint32 hp
= defender
->getScores()._PhysicalScores
[ SCORES::hit_points
].Current
;
568 nlwarning("<CCombatPhrase::validate> Entity %s is dead", TheDataset
.getEntityId(_Defender
->getEntityRowId()).toString().c_str());
569 //errorCode = "BS_TARGET_DEAD";
570 PHRASE_UTILITIES::sendSimpleMessage(_Attacker
->getEntityRowId(), "BS_TARGET_DEAD");
572 // PHRASE_UTILITIES::sendEngageFailedMessage( TheDataset.getEntityId(_Attacker->getEntityRowId()) );
574 // _BeingProcessed = false;
577 _CurrentTargetIsValid
= false;
582 // check combat float mode
583 CCharacter
*character
= PlayerManager
.getChar(_Attacker
->getEntityRowId());
584 if (character
&& !character
->getCombatFloatMode())
586 if (!_TargetTooFarMsg
)
588 PHRASE_UTILITIES::sendSimpleMessage( _Attacker
->getEntityRowId(), "BS_TARGET_TOO_FAR_OR");
589 _TargetTooFarMsg
= true;
593 INFOLOG("<CCombatPhrase::validate> Entity %s is isn't engaged IDLE mode",TheDataset
.getEntityId(_Attacker
->getEntityRowId()).toString().c_str());
599 double range
= weapon
.Range
+ ammo
.Range
;
600 double distance
= PHRASE_UTILITIES::getDistance(_Attacker
->getEntityRowId(), defender
->getEntityRowId());
601 if ( distance
> range
)
603 if (!_TargetTooFarMsg
)
605 PHRASE_UTILITIES::sendSimpleMessage( _Attacker
->getEntityRowId(), "BS_TARGET_TOO_FAR");
606 _TargetTooFarMsg
= true;
610 INFOLOG("<CCombatPhrase::validate> Entity %s is too far from it's target %s",TheDataset
.getEntityId(_Attacker
->getEntityRowId()).toString().c_str(), defender
->getId().toString().c_str());
615 // check the player has engaged a target if not auto engage the target
616 if (!engaged
&& _CurrentTargetIsValid
)
618 // Test entity can engage combat right now
619 /* if ( ! PhraseManager->canEntityEngageCombat(TheDataset.getEntityId(_Attacker->getEntityRowId())) )
621 //errorCode = "EGS_CANNOT_ENGAGE_COMBAT_YET";
622 nlwarning("<CCombatPhrase::validate> Entity %s cannot engage combat yet", TheDataset.getEntityId(_Attacker->getEntityRowId()).toString().c_str() );
626 // engage the target and continue
627 /* const double d = PHRASE_UTILITIES::getDistance( TheDataset.getEntityId(_Attacker->getEntityRowId()), TheDataset.getEntityId(_Defender->getEntityRowId()) );
628 if (d >= MaxEngageMeleeDistance )
630 PHRASE_UTILITIES::sendEngageFailedMessage(TheDataset.getEntityId(_Attacker->getEntityRowId()));
631 //errorCode = "BS_TARGET_TOO_FAR";
637 if ( ! PHRASE_UTILITIES::engageTargetInMelee( TheDataset
.getEntityId(_Attacker
->getEntityRowId()), TheDataset
.getEntityId(_Defender
->getEntityRowId()) ) )
639 _BeingProcessed
= false;
640 DEBUGLOG("<CCombatPhrase::validate> Entity %u Failed to engage its target in melee combat", _Attacker
->getEntityRowId().getIndex() );
646 if ( ! PHRASE_UTILITIES::engageTargetRange( TheDataset
.getEntityId(_Attacker
->getEntityRowId()), TheDataset
.getEntityId(_Defender
->getEntityRowId()) ) )
648 DEBUGLOG("<CCombatPhrase::validate> Entity %u Failed to engage its target in range combat", _Attacker
->getEntityRowId().getIndex() );
649 _BeingProcessed
= false;
656 if(!checkPhraseCost(errorCode
))
658 if (!_NotEnoughStaminaMsg
&& errorCode
== "EGS_TOO_EXPENSIVE_STAMINA")
660 _NotEnoughStaminaMsg
= true;
661 PHRASE_UTILITIES::sendSimpleMessage( _Attacker
->getEntityRowId(), errorCode
);
663 else if (!_NotEnoughHpMsg
&& errorCode
== "EGS_TOO_EXPENSIVE_HP")
665 _NotEnoughHpMsg
= true;
666 PHRASE_UTILITIES::sendSimpleMessage( _Attacker
->getEntityRowId(), errorCode
);
671 if (!validateCombatActions(errorCode
))
673 PHRASE_UTILITIES::sendSimpleMessage( _Attacker
->getEntityRowId(), errorCode
);
674 DEBUGLOG("<CCombatPhrase::validate> Entity %u Failed to validate combat actions, error = %s", _Attacker
->getEntityRowId().getIndex(), errorCode
.c_str() );
679 // set the attacks flag of the acting entity
680 actingEntity
->setActionFlag( RYZOMACTIONFLAGS::Attacks
, true );
683 _BeingProcessed
= false;
688 //--------------------------------------------------------------
690 //--------------------------------------------------------------
691 bool CCombatPhrase::update()
693 _BeingProcessed
= true;
695 const NLMISC::TGameCycle time
= CTickEventHandler::getGameCycle();
697 // if the sentence execution delay time has ended, apply sentence effects
698 if ( _State
== SecondValidated
&& _ExecutionEndDate
<= time
&& _NbWaitingRequests
== 0 && !_Idle
)
702 else if ( _State
== Validated
|| _State
== SecondValidated
|| _State
== ExecutionInProgress
)
706 CEntityBase
*actor
= _Attacker
->getEntity();
709 _BeingProcessed
= false;
710 // cannot reset the action flag, the actor cannot be found...
714 // check is actor is stunned
715 if (actor
->isStunned())
720 actor
->setActionFlag( RYZOMACTIONFLAGS::Attacks
, false );
722 // check target validity
723 TDataSetRow target
= TheDataset
.getDataSetRow(actor
->getTarget());
724 if (!_Defender
|| !TheDataset
.isDataSetRowStillValid(_Defender
->getEntityRowId()) || target
!= _Defender
->getEntityRowId())
726 createDefender(target
);
730 _CurrentTargetIsValid
=false;
736 _CurrentTargetIsValid
= checkTargetValidity( _Defender
->getEntityRowId(), errorCode
);
737 if ( !_CurrentTargetIsValid
)
739 PHRASE_UTILITIES::sendSimpleMessage(_Attacker
->getEntityRowId(), errorCode
);
746 if ( ! PHRASE_UTILITIES::engageTargetInMelee( TheDataset
.getEntityId(_Attacker
->getEntityRowId()), TheDataset
.getEntityId(_Defender
->getEntityRowId()) ) )
748 DEBUGLOG("<CCombatPhrase::validate> Entity %u Failed to engage its target in melee combat", _Attacker
->getEntityRowId().getIndex() );
749 _BeingProcessed
= false;
755 if ( ! PHRASE_UTILITIES::engageTargetRange( TheDataset
.getEntityId(_Attacker
->getEntityRowId()), TheDataset
.getEntityId(_Defender
->getEntityRowId()) ) )
757 DEBUGLOG("<CCombatPhrase::validate> Entity %u Failed to engage its target in range combat", _Attacker
->getEntityRowId().getIndex() );
758 _BeingProcessed
= false;
767 if (!_CurrentTargetIsValid
)
773 if(!checkPhraseCost(errorCode
))
775 if (!_NotEnoughStaminaMsg
&& errorCode
== "EGS_TOO_EXPENSIVE_STAMINA" && !_Idle
)
777 _NotEnoughStaminaMsg
= true;
778 PHRASE_UTILITIES::sendSimpleMessage( _Attacker
->getEntityRowId(), errorCode
);
780 else if (!_NotEnoughHpMsg
&& errorCode
== "EGS_TOO_EXPENSIVE_HP" && !_Idle
)
782 _NotEnoughHpMsg
= true;
783 PHRASE_UTILITIES::sendSimpleMessage( _Attacker
->getEntityRowId(), errorCode
);
789 // check combat float mode
790 if ( _CurrentTargetIsValid
&& TheDataset
.getEntityId(_Attacker
->getEntityRowId()).getType() == RYZOMID::player
)
794 CCharacter
*character
= dynamic_cast<CCharacter
*> (actor
);
795 if (character
&& !character
->getCombatFloatMode())
797 if (!_TargetTooFarMsg
&& !_Idle
)
799 PHRASE_UTILITIES::sendSimpleMessage( _Attacker
->getEntityRowId(), "BS_TARGET_TOO_FAR_OR");
800 _TargetTooFarMsg
= true;
808 CCombatWeapon weapon
;
810 _Attacker
->getItem( CCombatAttacker::RightHandItem
, weapon
);
811 _Attacker
->getItem( CCombatAttacker::Ammo
, ammo
);
814 double range
= weapon
.Range
+ ammo
.Range
;
815 double distance
= PHRASE_UTILITIES::getDistance(_Attacker
->getEntityRowId(), _Defender
->getEntityRowId());
816 if ( distance
> range
)
818 if (!_TargetTooFarMsg
)
820 PHRASE_UTILITIES::sendSimpleMessage( _Attacker
->getEntityRowId(), "BS_TARGET_TOO_FAR");
821 _TargetTooFarMsg
= true;
825 INFOLOG("<CCombatPhrase::validate> Entity %s is too far from it's target %s",TheDataset
.getEntityId(_Attacker
->getEntityRowId()).toString().c_str(), _Defender
->getEntity()->getId().toString().c_str());
830 actor
->setActionFlag( RYZOMACTIONFLAGS::Attacks
, true );
832 else if ( _State
== Latent
&& _LatencyEndDate
<= time
)
834 INFOLOG("Latency ended");
838 _BeingProcessed
= false;
843 //--------------------------------------------------------------
845 //--------------------------------------------------------------
846 void CCombatPhrase::execute()
848 if( _Idle
|| _NbWaitingRequests
!= 0)
851 const NLMISC::TGameCycle time
= CTickEventHandler::getGameCycle();
853 _State
= CSPhrase::ExecutionInProgress
;
855 _ExecutionEndDate
= time
+ _ExecutionLengthModifier
;// + uint32(sentenceLatency / CTickEventHandler::getGameTimeStep()) ;
859 CCharacter
* player
= dynamic_cast<CCharacter
*> (_Attacker
->getEntity());
861 player
->setCurrentAction(CLIENT_ACTION_TYPE::Combat
,_ExecutionEndDate
);
865 //--------------------------------------------------------------
867 //--------------------------------------------------------------
868 void CCombatPhrase::apply()
870 if ( !_Attacker
|| !_Defender
)
872 nlwarning("<CCombatPhrase::validate> Found NULL attacker or defender.");
877 CEntityBase
* actingEntity
= PHRASE_UTILITIES::entityPtrFromId( _Attacker
->getEntityRowId() );
878 if (actingEntity
== NULL
)
880 nlwarning("<CCombatPhrase::apply> Invalid entity Id %s", TheDataset
.getEntityId(_Attacker
->getEntityRowId()).toString().c_str() );
881 _BeingProcessed
= false;
885 actingEntity
->setActionFlag( RYZOMACTIONFLAGS::Attacks
, false );
887 CEntityBase
*defender
= _Defender
->getEntity();
890 nlwarning("<CCombatPhrase::apply> Cannot get entity base pointer, error");
895 CCombat
*combat
= PhraseManager
->getCombatInitiatedBy( _Attacker
->getEntityRowId() );
899 _BeingProcessed
= true;
900 _TargetTooFarMsg
= false;
902 _State
= CSPhrase::Latent
;
903 _ExecutionBehaviour
.Data
= 0;
905 _AiEventReport
.init();
907 _AiEventReport
.Originator
= _Attacker
->getEntityRowId();
908 _AiEventReport
.Target
= _Defender
->getEntityRowId();
909 _AiEventReport
.Type
= ACTNATURE::OFFENSIVE
;
912 if (_StaminaCost
!= 0)
914 SCharacteristicsAndScores
&stamina
= actingEntity
->getScores()._PhysicalScores
[SCORES::stamina
];
915 if ( stamina
.Current
!= 0 )
917 // nlinfo("Stamina current = %d, cost %d", stamina.Current.getValue(), _StaminaCost);
918 stamina
.Current
= stamina
.Current
- _StaminaCost
;
919 if (stamina
.Current
< 0)
926 actingEntity
->changeCurrentHp( (_HPCost
) * (-1) );
929 CCombatWeapon weapon
;
931 if ( _Attacker
->getItem( CCombatAttacker::RightHandItem
, weapon
) )
933 if (weapon
.Family
== ITEMFAMILY::MELEE_WEAPON
)
937 else if (weapon
.Family
== ITEMFAMILY::RANGE_WEAPON
)
939 if ( _Attacker
->getItem( CCombatAttacker::Ammo
, ammo
) )
942 if ( !_Attacker
->checkAmmoAmount() )
944 PHRASE_UTILITIES::sendSimpleMessage( _Attacker
->getEntityRowId(), "EGS_NOT_ENOUGH_AMMO");
945 _BeingProcessed
= false;
950 _Attacker
->lockAmmos();
954 PHRASE_UTILITIES::sendSimpleMessage( _Attacker
->getEntityRowId(), "BS_NO_AMMO");
955 _BeingProcessed
= false;
961 // ERROR -> Not a weapon
962 PHRASE_UTILITIES::sendSimpleMessage(_Attacker
->getEntityRowId(), "BS_ITEM_INCOMPATIBLE");
963 _BeingProcessed
= false;
967 weapon
.SkillValue
= _Attacker
->getSkillValue(weapon
.Skill
);
971 weapon
.Damage
= HandToHandDamage
;
972 weapon
.DmgType
= DMGTYPE::BLUNT
;
973 weapon
.SpeedInTicks
= HandToHandSpeed
;
975 // TODO skill BareHandCombat missing
976 // weapon.Quality = (uint16)PHRASE_UTILITIES::getEntityLevel( _Attacker->getEntityRowId(), SKILLS::BareHandCombat);
977 weapon
.Quality
= (uint16
)PHRASE_UTILITIES::getEntityLevel( _Attacker
->getEntityRowId(), SKILLS::SFM1H
);
980 //weapon.SkillValue = 10;//_Attacker->getEntity()->getSkills()._Skills[ SKILLS::BareHandCombat ].Current;//(uint16)PHRASE_UTILITIES::getEntityLevel( _Attacker->getEntityRowId(), SKILLS::BareHandCombat);
981 weapon
.SkillValue
= _Attacker
->getEntity()->getSkills()._Skills
[ SKILLS::SFM1H
].Current
;
982 weapon
.Skill
= SKILLS::SFM1H
;//SKILLS::BareHandCombat;
986 // compute latency end date
987 const NLMISC::TGameCycle time
= CTickEventHandler::getGameCycle();
988 _LatencyEndDate
= time
+ _HitRateModifier
+ weapon
.SpeedInTicks
+ ammo
.SpeedInTicks
;
990 // test phrase success
992 if (!testPhraseSuccess())
996 _ExecutionBehaviour
.Combat
.DamageType
= (ammo
.Damage
!= 0) ? ammo
.DmgType
: weapon
.DmgType
;
997 _ExecutionBehaviour
.Combat
.AttackIntensity
= PHRASE_UTILITIES::getAttackIntensity(_SabrinaCost
/ 10);
998 _ExecutionBehaviour
.Combat
.ImpactIntensity
= 0;
999 _ExecutionBehaviour
.Combat
.KillingBlow
= 0;
1001 if (_PhraseSuccessDamageFactor
== 0.0f
)
1004 combat
->pushEvent( COMBAT_HISTORY::Miss
);
1005 PHRASE_UTILITIES::sendCombatResistMessages( _Attacker
->getEntityRowId(), _Defender
->getEntityRowId() );
1007 else if (_PhraseSuccessDamageFactor
< 0.0f
)
1010 combat
->pushEvent( COMBAT_HISTORY::Fumble
);
1011 PHRASE_UTILITIES::sendFumbleMessage( _Attacker
->getEntityRowId(), _Defender
->getEntityRowId() );
1013 _LatencyEndDate
= (_LatencyEndDate
-time
) * 2 + time
;
1019 CCombatShield shield
;
1020 _Defender
->getShield(shield
);
1022 // determine localisation
1023 PHRASE_UTILITIES::TPairSlotShield localisation
;
1024 sint8 adjustment
= PHRASE_UTILITIES::getLocalisationSizeAdjustement( _Attacker
->getEntityRowId(), _Defender
->getEntityRowId() );
1025 if ( _ForcedLocalisation
!= SLOT_EQUIPMENT::UNDEFINED
)
1026 localisation
= PHRASE_UTILITIES::getLocalisation( shield
.ShieldType
, adjustment
, _ForcedLocalisation
);
1028 localisation
= PHRASE_UTILITIES::getLocalisation( shield
.ShieldType
, adjustment
);
1030 // test against opponent defense
1031 if ( testOpponentDefense( _Defender
->getEntityRowId(), localisation
) )
1033 // opponent dodged the attack, send messages
1034 DEBUGLOG("<CCombatPhrase::execute> Actor %u, target %u has dodged", _Attacker
->getEntityRowId().getIndex(), _Defender
->getEntityRowId().getIndex() );
1036 // update the actor behaviour
1037 _ExecutionBehaviour
.Combat
.DamageType
= (ammo
.Damage
!= 0) ? ammo
.DmgType
: weapon
.DmgType
;
1038 _ExecutionBehaviour
.Combat
.AttackIntensity
= PHRASE_UTILITIES::getAttackIntensity(_SabrinaCost
/ 10);
1039 _ExecutionBehaviour
.Combat
.ImpactIntensity
= 0;
1040 _ExecutionBehaviour
.Combat
.KillingBlow
= 0;
1042 if (defender
->dodgeAsDefense())
1043 combat
->pushEvent( COMBAT_HISTORY::Dodge
);
1045 combat
->pushEvent( COMBAT_HISTORY::Parry
);
1049 if (_PhraseSuccessDamageFactor
> 1.0f
)
1052 PHRASE_UTILITIES::sendCriticalHitMessage( _Attacker
->getEntityRowId(), _Defender
->getEntityRowId() );
1056 sint32 attackerLevel
;
1058 uint16 itemQuality
= _MeleeCombat
? weapon
.Quality
: ammo
.Quality
;
1059 if ( (weapon
.SkillValue
/10) >= itemQuality
)
1061 // attacker level is the same or higher, compute the mean between attacker and weapon level
1062 attackerLevel
= (weapon
.SkillValue
+ itemQuality
*10)/2;
1066 // attacker level is lower, give him a +10 bonus to skill
1067 attackerLevel
= 10 + weapon
.SkillValue
;
1070 sint32 weaponDamage
= _MeleeCombat
? weapon
.Damage
: ammo
.Damage
;
1071 damage
= sint32( ( weaponDamage
* ( 10.0f
+ float(attackerLevel
)) / 260.0f
+ _DamageModifier
) * _PhraseSuccessDamageFactor
);
1072 sint32 damageBeforeArmor
= damage
;
1074 // get damage amplifier on target
1075 const CSEffect
* effect
= defender
->lookForSEffect( EFFECT_FAMILIES::MeleeDmgAmpli
);
1077 damage
*= effect
->getParamValue() / 100;
1081 _Defender
->getArmor( localisation
.first
, armor
);
1083 // compute armor and shield protections
1084 sint32 defenderArmorLevel
= ((armor
.SkillValue
/10) >= armor
.Quality
) ? ((weapon
.SkillValue
+ armor
.Quality
)*10)/2 : 10+armor
.SkillValue
;
1085 sint32 defenderShieldLevel
= ((shield
.SkillValue
/10) >= shield
.Quality
) ? ((shield
.SkillValue
+ shield
.Quality
)*10)/2 : 10+shield
.SkillValue
;
1087 DMGTYPE::EDamageType dmgType
= (ammo
.Damage
!= 0) ? ammo
.DmgType
: weapon
.DmgType
;
1089 uint32 dmgPreventedByShield
= 0;
1090 uint32 dmgPreventedByArmor
= 0;
1092 // get modifier according to armor type
1093 float shieldAbsorptionFactor
= 1.0;
1094 sint32 shieldAbsorptionModifier
= 0;
1095 float armorAbsorptionFactor
= 1.0;
1096 sint32 armorAbsorptionModifier
= 0;
1098 switch(armor
.ArmorType
)
1100 case ARMORTYPE::LIGHT
:
1101 armorAbsorptionFactor
= _LightArmorAbsorptionMultiplier
;
1102 armorAbsorptionModifier
= _LightArmorAbsorptionModifier
;
1104 case ARMORTYPE::MEDIUM
:
1105 armorAbsorptionFactor
= _MediumArmorAbsorptionMultiplier
;
1106 armorAbsorptionModifier
= _MediumArmorAbsorptionModifier
;
1108 case ARMORTYPE::HEAVY
:
1109 armorAbsorptionFactor
= _HeavyArmorAbsorptionMultiplier
;
1110 armorAbsorptionModifier
= _HeavyArmorAbsorptionModifier
;
1115 switch(shield
.ArmorType
)
1117 case ARMORTYPE::LIGHT
:
1118 shieldAbsorptionFactor
= _LightArmorAbsorptionMultiplier
;
1119 shieldAbsorptionModifier
= _LightArmorAbsorptionModifier
;
1121 case ARMORTYPE::MEDIUM
:
1122 shieldAbsorptionFactor
= _MediumArmorAbsorptionMultiplier
;
1123 shieldAbsorptionModifier
= _MediumArmorAbsorptionModifier
;
1125 case ARMORTYPE::HEAVY
:
1126 shieldAbsorptionFactor
= _HeavyArmorAbsorptionMultiplier
;
1127 shieldAbsorptionModifier
= _HeavyArmorAbsorptionModifier
;
1134 case DMGTYPE::BLUNT
:
1135 if (localisation
.second
)
1137 dmgPreventedByShield
+= (uint32
)(shield
.MaxBluntProtection
< (uint32
)(0.01 * shield
.BluntProtectionFactor
* damage
) ? shield
.MaxBluntProtection
: 0.01 * shield
.BluntProtectionFactor
* damage
);
1138 dmgPreventedByShield
= (uint32
) ((10.0f
+ float(defenderShieldLevel
))/260.0f
* dmgPreventedByShield
);
1139 dmgPreventedByShield
= max( sint32(0), sint32(dmgPreventedByShield
* shieldAbsorptionFactor
) + shieldAbsorptionModifier
);
1140 damage
-= dmgPreventedByShield
;
1142 dmgPreventedByArmor
+= (uint32
)(armor
.MaxBluntProtection
< (uint32
)(0.01 * armor
.BluntProtectionFactor
* damage
) ? armor
.MaxBluntProtection
: 0.01 * armor
.BluntProtectionFactor
* damage
);
1143 dmgPreventedByArmor
= (uint32
) ((10.0f
+ float(defenderArmorLevel
))/260.0f
* dmgPreventedByArmor
);
1144 dmgPreventedByArmor
= max( sint32(0), sint32(dmgPreventedByArmor
* armorAbsorptionFactor
) + armorAbsorptionModifier
);
1145 damage
-= dmgPreventedByArmor
;
1147 case DMGTYPE::SLASHING
:
1148 if (localisation
.second
)
1150 dmgPreventedByShield
+= (uint32
)(shield
.MaxSlashingProtection
< (uint32
)(0.01 * shield
.SlashingProtectionFactor
* damage
) ? shield
.MaxSlashingProtection
: 0.01 * shield
.SlashingProtectionFactor
* damage
);
1151 dmgPreventedByShield
= (uint32
) ((10.0f
+ float(defenderShieldLevel
))/260.0f
* dmgPreventedByShield
);
1152 dmgPreventedByShield
= max( sint32(0), sint32(dmgPreventedByShield
* shieldAbsorptionFactor
) + shieldAbsorptionModifier
);
1153 damage
-= dmgPreventedByShield
;
1155 dmgPreventedByArmor
+= (uint32
)(armor
.MaxSlashingProtection
< (uint32
)(0.01 * armor
.SlashingProtectionFactor
* damage
) ? armor
.MaxSlashingProtection
: 0.01 * armor
.SlashingProtectionFactor
* damage
);
1156 dmgPreventedByArmor
= (uint32
) ((10.0f
+ float(defenderArmorLevel
))/260.0f
* dmgPreventedByArmor
);
1157 dmgPreventedByArmor
= max( sint32(0), sint32(dmgPreventedByArmor
* armorAbsorptionFactor
) + armorAbsorptionModifier
);
1158 damage
-= dmgPreventedByArmor
;
1160 case DMGTYPE::PIERCING
:
1161 if (localisation
.second
)
1163 dmgPreventedByShield
+= (uint32
)(shield
.MaxPiercingProtection
< (uint32
)(0.01 * shield
.PiercingProtectionFactor
* damage
) ? shield
.MaxPiercingProtection
: 0.01 * shield
.PiercingProtectionFactor
* damage
);
1164 dmgPreventedByShield
= (uint32
) ((10.0f
+ float(defenderShieldLevel
))/260.0f
* dmgPreventedByShield
);
1165 dmgPreventedByShield
= max( sint32(0), sint32(dmgPreventedByShield
* shieldAbsorptionFactor
) + shieldAbsorptionModifier
);
1166 damage
-= dmgPreventedByShield
;
1168 dmgPreventedByArmor
+= (uint32
)(armor
.MaxPiercingProtection
< (uint32
)(0.01 * armor
.PiercingProtectionFactor
* damage
) ? armor
.MaxPiercingProtection
: 0.01 * armor
.PiercingProtectionFactor
* damage
);
1169 dmgPreventedByArmor
= (uint32
) ((10.0f
+ float(defenderArmorLevel
))/260.0f
* dmgPreventedByArmor
);
1170 dmgPreventedByArmor
= max( sint32(0), sint32(dmgPreventedByArmor
* armorAbsorptionFactor
) + armorAbsorptionModifier
);
1171 damage
-= dmgPreventedByArmor
;
1177 if ( dmgPreventedByShield
!= 0)
1179 INFOLOG("Entity %u (attacked by %u) Damage prevented by the shield = %u", _Defender
->getEntityRowId().getIndex(), _Attacker
->getEntityRowId().getIndex(), dmgPreventedByShield
);
1180 // remove shield hit points
1181 // compute the real _DamageAmount of hp removed for the shield
1182 _Defender
->damageOnShield( dmgPreventedByShield
);
1183 //_ShieldLostHp[targetIndex] += (uint32) (dmgPreventedByShield * ( 1 + shieldDeltaLevel * 0.1));
1184 //sentence->LogReportStructure.ShieldAbsorption += dmgPreventedByShield;
1187 if ( dmgPreventedByArmor
!= 0)
1189 _Defender
->damageOnArmor(localisation
.first
, dmgPreventedByArmor
);
1190 INFOLOG("Entity %u (attacked by %u) Damage prevented by the armor = %u",_Defender
->getEntityRowId().getIndex(), _Attacker
->getEntityRowId().getIndex(), dmgPreventedByArmor
);
1193 _ExecutionBehaviour
.Combat
.DamageType
= dmgType
;
1194 _ExecutionBehaviour
.Combat
.AttackIntensity
= PHRASE_UTILITIES::getAttackIntensity(_SabrinaCost
/ 10);
1195 _ExecutionBehaviour
.Combat
.ImpactIntensity
= PHRASE_UTILITIES::getImpactIntensity( damage
, _Defender
->getEntityRowId() );
1199 // MAY BE AN BUG -> LOG MANY INFOS FOR DEBUG
1200 nlwarning("COMBAT : Entity %s hits entity %s but does 0 damage.", actingEntity
->getId().toString().c_str(), defender
->getId().toString().c_str() );
1201 nlwarning("Attacker weapon infos : %s", weapon
.toString());
1202 nlwarning("attackerLevel = %d, weaponDamage : %d", attackerLevel
, weaponDamage
);
1204 nlwarning("Defender armor infos : %s", armor
.toString());
1205 nlwarning("Damage before effects and armor : %d", damageBeforeArmor
);
1206 nlwarning("Damage prevented by armor : %d", dmgPreventedByArmor
);
1207 nlwarning("Damage prevented by shield : %d", dmgPreventedByShield
);
1213 applyCombatActions();
1215 _ExecutionBehaviour
.DeltaHP
= (sint16
)((-1)*damage
);
1217 if ( defender
->changeCurrentHp( (-1)*damage
) == true)
1219 // entity has been killed, change the bahaviour of the attacker to set the flag
1220 _ExecutionBehaviour
.Combat
.KillingBlow
= 1;
1222 // send mission event
1223 if ( actingEntity
->getId().getType()== RYZOMID::player
)
1225 CMissionEventKill
event ( _Defender
->getEntityRowId() );
1226 ((CCharacter
*) actingEntity
)->processMissionEvent( event
);
1231 CPhraseManager::getInstance()->breakCast(attackerLevel
,actingEntity
,defender
);
1234 // add modifier to sentence AI event reports
1238 //PHRASE_UTILITIES::testSpellBreakOnDamage( _Defender->getEntityRowId(), _Attacker->getEntityRowId(), damage, damageType);
1241 sint32 lostStamina
= (sint32
)( damage
* _StaminaLossFactor
+ _StaminaLossModifier
);
1242 sint32 lostSap
= (sint32
)(damage
* _SapLossFactor
+ _SapLossModifier
);
1244 if ( lostStamina
!= 0 )
1246 INFOLOG("entity %s lose %d stamina", TheDataset
.getEntityId(_Defender
->getEntityRowId()).toString().c_str(),lostStamina
);
1247 defender
->getPhysScores()._PhysicalScores
[SCORES::stamina
].Current
= defender
->getPhysScores()._PhysicalScores
[SCORES::stamina
].Current
- lostStamina
;
1251 if ( defender
->getPhysScores()._PhysicalScores
[SCORES::stamina
].Current
< 0 )
1252 defender
->getPhysScores()._PhysicalScores
[SCORES::stamina
].Current
= 0;
1254 // add modifier to sentence AI event reports
1255 _AiEventReport
.addDelta(AI_EVENT_REPORT::Stamina
, (-1)*lostStamina
);
1259 //value = toString( _BaseSapAbsorption[i] );
1260 INFOLOG("entity %s lose %d sap", TheDataset
.getEntityId(_Defender
->getEntityRowId()).toString().c_str(), lostSap
);
1261 defender
->getPhysScores()._PhysicalScores
[SCORES::sap
].Current
= defender
->getPhysScores()._PhysicalScores
[SCORES::sap
].Current
- lostSap
;
1265 if ( defender
->getPhysScores()._PhysicalScores
[SCORES::sap
].Current
< 0 )
1266 defender
->getPhysScores()._PhysicalScores
[SCORES::sap
].Current
= 0;
1268 // add modifier to sentence AI event reports
1269 _AiEventReport
.addDelta(AI_EVENT_REPORT::Sap
, (-1)*lostSap
);
1272 // send chat messages
1273 PHRASE_UTILITIES::sendHitMessages(_Attacker
->getEntityRowId(),_Defender
->getEntityRowId(), damage
, lostStamina
, lostSap
);
1275 if ( _ExecutionBehaviour
.Combat
.KillingBlow
== 1 )
1276 PHRASE_UTILITIES::sendDeathMessages( _Attacker
->getEntityRowId(), _Defender
->getEntityRowId() );
1278 // send report for xp gain if actor is a player
1279 if (_Attacker
->getEntity()->getId().getType() == RYZOMID::player
)
1280 ((CCharacter
*)(_Attacker
->getEntity()))->actionReport( _Defender
->getEntity(), _DeltaLevel
, ACTNATURE::OFFENSIVE
, SKILLS::toString(_AttackSkill
) );
1284 PHRASE_UTILITIES::sendHitNullMessages( _Attacker
->getEntityRowId(), _Defender
->getEntityRowId() );
1286 INFOLOG("Entity %s hits entity %s does %u damage", TheDataset
.getEntityId(_Attacker
->getEntityRowId()).toString().c_str(), TheDataset
.getEntityId(_Defender
->getEntityRowId()).toString().c_str(), damage
);
1289 // --- TODO : INSERT THE REAL HIT LOCATION + CRITICAL HIT MANAGEMENT
1290 if ( !_ExecutionBehaviour
.Combat
.KillingBlow
)
1291 combat
->pushEvent( COMBAT_HISTORY::HitChest
);
1296 // update the actor behaviour
1297 if ( _ExecutionBehaviour
.Behaviour
!= MBEHAV::UNKNOWN_BEHAVIOUR
)
1298 PHRASE_UTILITIES::sendUpdateBehaviour( _Attacker
->getEntityRowId(), _ExecutionBehaviour
);
1302 const sint32 maxPv
= defender
->getPhysScores()._PhysicalScores
[SCORES::hit_points
].Max
;
1305 aggro
= (-1) * sint32((100.0 * double(damage
+ _AggroModifier
))/double(maxPv
) * _AggroMultiplier
) ;
1309 _AiEventReport
.AggroMul
= 1.0f
;
1310 _AiEventReport
.AggroAdd
= aggro
;
1311 _AiEventReport
.addDelta(AI_EVENT_REPORT::HitPoints
, (-1)*damage
);
1313 PhraseManager
->addAiEventReport(_AiEventReport
);
1315 CCharacter
* player
= dynamic_cast<CCharacter
*> (_Attacker
->getEntity());
1317 player
->setCurrentAction(CLIENT_ACTION_TYPE::Combat
,_LatencyEndDate
);
1319 actingEntity
->setActionFlag( RYZOMACTIONFLAGS::Attacks
, false );
1321 _BeingProcessed
= false;
1324 //--------------------------------------------------------------
1326 //--------------------------------------------------------------
1327 void CCombatPhrase::end()
1329 if (!_Attacker
) return;
1331 _BeingProcessed
= true;
1333 _Attacker
->unlockRightItem();
1335 // set the attacks flag of the acting entity
1336 CEntityBase
*entityPtr
= PHRASE_UTILITIES::entityPtrFromId(_Attacker
->getEntityRowId());
1338 entityPtr
->setActionFlag( RYZOMACTIONFLAGS::Attacks
, false );
1340 _State
= CSPhrase::LatencyEnded
;
1342 if (_DisengageOnEnd
)
1344 PhraseManager
->disengage( _Attacker
->getEntityRowId(), false, true);
1347 _BeingProcessed
= false;
1349 CCharacter
* player
= dynamic_cast<CCharacter
*> (entityPtr
);
1351 player
->clearCurrentAction();
1357 //--------------------------------------------------------------
1358 // testOpponentDefense()
1359 //--------------------------------------------------------------
1360 bool CCombatPhrase::testOpponentDefense(const TDataSetRow
&targetRowId
, const PHRASE_UTILITIES::TPairSlotShield
&localisation
)
1362 sint32 attackerLevel
= 0;
1363 CCombatWeapon weapon
;
1364 if (_Attacker
->getItem( CCombatAttacker::RightHandItem
, weapon
) )
1366 _AttackSkill
= weapon
.Skill
;
1367 attackerLevel
= _AttackSkillModifier
+ ((weapon
.SkillValue
/10) >= weapon
.Quality
) ? ((weapon
.SkillValue
+ weapon
.Quality
)*10)/2 : 10 + weapon
.SkillValue
;
1368 attackerLevel
/= 10;
1372 if ( _RootSkill
== SKILLS::unknown
)
1373 //TODO barehandedcombat
1374 _AttackSkill
= SKILLS::SFM1H
;//SKILLS::BareHandCombat;
1376 _AttackSkill
= _RootSkill
;
1378 attackerLevel
= PHRASE_UTILITIES::getEntityLevel(_Attacker
->getEntityRowId(), _AttackSkill
,_AttackSkillModifier
);
1381 // defender has a malus of one level per attacker beyond the first one
1382 const set
<TDataSetRow
> &aggressors
= PhraseManager
->getMeleeAggressors(targetRowId
);
1384 sint32 nb
= aggressors
.size();
1388 sint32 valueTemp
= _Defender
->getDefenseValue();
1389 sint32 defenderLevel
= max( sint32((valueTemp
+ malus
) / 10), (sint32
)0);
1391 _DeltaLevel
= attackerLevel
- defenderLevel
;
1394 uint8 chances
= PHRASE_UTILITIES::getSuccessChance( defenderLevel
- attackerLevel
);
1396 // TEMP HACK : limit dodge chances for the moment, wait new rules (more 'fun')
1400 float result
= PHRASE_UTILITIES::getSucessFactor(chances
, (uint8
)RandomGenerator
.rand(99) );
1404 if ( !_Defender
->getEntity()->dodgeAsDefense() )
1406 PHRASE_UTILITIES::sendMessage( _Attacker
->getEntityRowId(), string("EGS_ACTOR_COMBAT_PARRY_E"), targetRowId
);
1407 PHRASE_UTILITIES::sendMessage( targetRowId
, string("EGS_TARGET_COMBAT_PARRY_E"), _Attacker
->getEntityRowId());
1411 PHRASE_UTILITIES::sendMessage( _Attacker
->getEntityRowId(), string("EGS_ACTOR_COMBAT_DODGE_E"), targetRowId
);
1412 PHRASE_UTILITIES::sendMessage( targetRowId
, string("EGS_TARGET_COMBAT_DODGE_E"), _Attacker
->getEntityRowId());
1419 } // testOpponentDefense //
1422 //--------------------------------------------------------------
1423 // testPhraseSuccess()
1424 //--------------------------------------------------------------
1425 bool CCombatPhrase::testPhraseSuccess()
1428 if ( _RootSkill
!= SKILLS::unknown
)
1429 skillValue
= _Attacker
->getSkillValue( _RootSkill
);
1432 //TODO skill barehanded combat
1433 CCombatWeapon weapon
;
1434 if (_Attacker
->getItem( CCombatAttacker::RightHandItem
, weapon
) )
1436 skillValue
= weapon
.SkillValue
;
1440 //skillValue = _Attacker->getSkillValue( SKILLS::BareHandCombat );
1441 skillValue
= _Attacker
->getSkillValue( SKILLS::SFM1H
);
1445 const sint16 relativeLevel
= (skillValue
+ _SabrinaCredit
- 2*_SabrinaCost
) / 10;
1448 uint8 chances
= PHRASE_UTILITIES::getSuccessChance( relativeLevel
);
1450 _PhraseSuccessDamageFactor
= PHRASE_UTILITIES::getSucessFactor(chances
, (uint8
)RandomGenerator
.rand(99) );
1451 // nlinfo("_PhraseSuccessDamageFactor = %f, (skill+bonus = %d, difficulty = %d)", _PhraseSuccessDamageFactor, (skillValue + _SabrinaCredit - _SabrinaCost), _SabrinaCost);
1453 if (_PhraseSuccessDamageFactor
== 0.0f
)
1458 else if (_PhraseSuccessDamageFactor
< 0.0f
)
1465 } // testPhraseSuccess //
1468 //--------------------------------------------------------------
1469 // checkTargetValidity()
1470 //--------------------------------------------------------------
1471 bool CCombatPhrase::checkTargetValidity( const TDataSetRow
&targetRowId
, string
&errorCode
)
1473 if ( !targetRowId
.isValid() || !TheDataset
.isDataSetRowStillValid(targetRowId
) )
1475 errorCode
= "BS_INVALID_TARGET";
1479 if (targetRowId
== _Attacker
->getEntityRowId() && TheDataset
.getEntityId(_Attacker
->getEntityRowId()).getType() == RYZOMID::player
)
1481 errorCode
= "BS_INVALID_TARGET";
1485 if ( ! PHRASE_UTILITIES::testOffensiveActionAllowed(_Attacker
->getEntityRowId(), targetRowId
, errorCode
) )
1491 } // checkTargetValidity //
1494 //--------------------------------------------------------------
1495 // checkPhraseCost()
1496 //--------------------------------------------------------------
1497 bool CCombatPhrase::checkPhraseCost( string
&errorCode
)
1499 if (!_Attacker
|| !_Attacker
->getEntity()) return false;
1501 // only check costs for players
1502 if ( TheDataset
.getEntityId(_Attacker
->getEntityRowId()).getType() != RYZOMID::player
)
1505 const SCharacteristicsAndScores
&stamina
= _Attacker
->getEntity()->getScores()._PhysicalScores
[SCORES::stamina
];
1506 if ( stamina
.Current
< _StaminaCost
)
1508 errorCode
= "EGS_TOO_EXPENSIVE_STAMINA";
1512 const SCharacteristicsAndScores
&hp
= _Attacker
->getEntity()->getScores()._PhysicalScores
[SCORES::hit_points
];
1513 if ( hp
.Current
< _HPCost
)
1515 errorCode
= "EGS_TOO_EXPENSIVE_HP";
1520 } // checkPhraseCost //
1523 //--------------------------------------------------------------
1525 //--------------------------------------------------------------
1526 void CCombatPhrase::createDefender( const TDataSetRow
&targetRowId
)
1528 if (_Defender
!= NULL
)
1534 if ( !targetRowId
.isValid() || !TheDataset
.isDataSetRowStillValid(targetRowId
) )
1537 // create defender structure
1538 if ( TheDataset
.getEntityId(targetRowId
).getType() == RYZOMID::player
)
1540 _Defender
= new CCombatDefenderPlayer(targetRowId
);
1544 _Defender
= new CCombatDefenderAI(targetRowId
);
1546 } // createDefender //
1549 //--------------------------------------------------------------
1550 // validateCombatActions()
1551 //--------------------------------------------------------------
1552 bool CCombatPhrase::validateCombatActions( string
&errorCode
)
1554 const uint size
= _CombatActions
.size();
1555 for (uint i
= 0 ; i
< size
; ++i
)
1557 if ( _CombatActions
[i
] != NULL
)
1559 if (!_CombatActions
[i
]->validate(this, errorCode
))
1565 } // validateCombatActions //
1567 //--------------------------------------------------------------
1568 // applyCombatActions()
1569 //--------------------------------------------------------------
1570 void CCombatPhrase::applyCombatActions()
1572 if (!_Defender
) return;
1574 const uint size
= _CombatActions
.size();
1575 for (uint i
= 0 ; i
< size
; ++i
)
1577 if ( _CombatActions
[i
] != NULL
)
1579 _CombatActions
[i
]->setTarget( _Defender
->getEntityRowId() );
1580 _CombatActions
[i
]->apply(this);
1583 } // validateCombatActions //