Resolve "Toggle Free Look with Hotkey"
[ryzomcore.git] / ryzom / server / src / sabrina / combat_phrase.cpp
bloba0bf5f4e04462c8a6ceac3c5e821e9099360aefa
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
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.
8 //
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/>.
20 //////////////
21 // INCLUDE //
22 //////////////
23 #include "stdpch.h"
24 #include "combat_phrase.h"
25 // net
26 #include "nel/net/message.h"
27 // misc
28 #include "nel/misc/common.h"
29 // game share
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"
46 //////////////
47 // USING //
48 //////////////
49 using namespace std;
50 using namespace NLNET;
51 using namespace NLMISC;
52 using namespace RY_GAME_SHARE;
55 //////////////
56 // EXTERN //
57 //////////////
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 );
70 New = 0,
71 Evaluated,
72 Validated,
73 Idle,
74 ExecutionInProgress,
75 SecondValidated,
76 WaitNextCycle,
77 Latent,
78 LatencyEnded,
80 UnknownState,
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);
89 if (attacker == NULL)
90 return false;
92 if (attacker->getId().getType() == RYZOMID::player )
94 _Attacker = new CCombatAttackerPlayer(actorRowId);
95 if (!_Attacker)
96 return false;
98 else
100 _Attacker = new CCombatAttackerAI(actorRowId);
101 if (!_Attacker)
102 return false;
106 // create defender structure -> wait first validation
108 // add bricks
109 for (uint i = 0; i < bricks.size(); i++)
111 addBrick( *bricks[i] );
114 return true;
119 //--------------------------------------------------------------
120 // destructor
121 //--------------------------------------------------------------
122 CCombatPhrase::~CCombatPhrase()
124 if (_Attacker != NULL)
125 delete _Attacker;
126 if (_Defender != NULL)
127 delete _Defender;
129 const uint size = _CombatActions.size();
130 for (uint i = 0 ; i < size ; ++i)
132 if (_CombatActions[i] != NULL)
133 delete _CombatActions[i];
138 //--------------------------------------------------------------
139 // constructor
140 //--------------------------------------------------------------
141 CCombatPhrase::CCombatPhrase(const CStaticBrick &rootBrick)
143 init();
144 //_RootSkill = rootBrick.Skill;
146 addBrick(rootBrick);
147 } // constructor //
149 //--------------------------------------------------------------
150 // init()
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;
165 _Attacker = NULL;
166 _Defender = NULL;
168 _RootSkill = SKILLS::unknown;
169 _AttackSkill = SKILLS::unknown;
171 _CyclicPhrase = false;
172 _Idle = false;
173 _TargetTooFarMsg = false;
174 _NotEnoughStaminaMsg = false;
175 _NotEnoughHpMsg = false;
176 _CurrentTargetIsValid = false;
177 _MeleeCombat = true;
179 _ExecutionEndDate = 0;
180 _LatencyEndDate = 0;
181 _NbWaitingRequests = 0;
183 _SabrinaCost = 0;
184 _SabrinaCredit = 0;
186 _HPCost = 0;
187 _StaminaCost = 0;
189 _AttackSkillModifier = 0;
190 _ExecutionLengthModifier = 0;
191 _HitRateModifier = 0;
192 _DamageModifier = 0;
193 _DeltaLevel = 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;
209 _AggroModifier = 0;
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;
221 } // init //
223 //--------------------------------------------------------------
224 // addBrick()
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;
242 // process params
243 unsigned i;
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;
251 break;
253 case TBrickParam::SAP:
254 //printf("SAP: %i\n",((CSBrickParamSap *)brick.Params[i])->Sap);
255 break;
257 case TBrickParam::STA:
258 // INFOLOG("STA: %i",((CSBrickParamSta *)brick.Params[i])->Sta);
259 _StaminaCost += ((CSBrickParamSta *)brick.Params[i])->Sta;
260 break;
262 case TBrickParam::EXECUTION_LENGTH:
263 // INFOLOG("EXECUTION_LENGTH: %i",((CSBrickParamExecutionLength *)brick.Params[i])->ExecutionLength);
264 _ExecutionLengthModifier += ((CSBrickParamExecutionLength *)brick.Params[i])->ExecutionLength;
265 break;
267 case TBrickParam::LATENCY_LENGTH:
268 // INFOLOG("LATENCY_LENGTH: %i",((CSBrickParamLatencyLength *)brick.Params[i])->LatencyLength);
269 _HitRateModifier += ((CSBrickParamLatencyLength *)brick.Params[i])->LatencyLength;
270 break;
272 case TBrickParam::DMG_MOD:
273 // INFOLOG("DMG_MOD: %i",((CSBrickParamDamageModifier *)brick.Params[i])->DamageModifier);
274 _DamageModifier += ((CSBrickParamDamageModifier *)brick.Params[i])->DamageModifier;
275 break;
277 case TBrickParam::DMG_MUL:
278 // INFOLOG("DMG_MUL: %i",((CSBrickParamDamageFactor *)brick.Params[i])->DamageFactor);
279 _DamageFactor += ((CSBrickParamDamageFactor *)brick.Params[i])->DamageFactor - 1.0f;
280 break;
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;
286 break;
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;
292 break;
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;
298 break;
300 case TBrickParam::ATT_SKILL_MOD:
301 // INFOLOG("ATT_SKILL_MOD: %f",((CSBrickParamAttackSkillModifier *)brick.Params[i])->AttackSkillModifier);
302 _AttackSkillModifier += ((CSBrickParamAttackSkillModifier *)brick.Params[i])->AttackSkillModifier;
303 break;
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 );
308 break;
310 case TBrickParam::OPENING:
311 INFOLOG("OPENING : %s",((CSBrickParamOpening *)brick.Params[i])->OpeningType.c_str());
312 _Opening = ((CSBrickParamOpening *)brick.Params[i])->OpeningType;
313 break;
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
319 break;
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);
326 break;
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));
340 break;
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;
346 break;
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;
352 break;
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;
358 break;
360 default:
361 break;
364 } // addBrick //
367 //--------------------------------------------------------------
368 // evaluate()
369 //--------------------------------------------------------------
370 bool CCombatPhrase::evaluate(CEvalReturnInfos *msg)
372 _State = CSPhrase::Evaluated;
374 _Idle = false;
375 _PhraseSuccessDamageFactor = 0.0f;
376 _AttackSkill = SKILLS::unknown;
377 _Validated = false;
378 _TargetTooFarMsg = false;
379 _NotEnoughStaminaMsg = false;
380 _NotEnoughHpMsg = false;
381 _DisengageOnEnd = false;
382 //_CurrentTargetIsValid = false;
383 //_MeleeCombat = true;
384 return true;
385 } // evaluate //
388 //--------------------------------------------------------------
389 // validate()
390 //--------------------------------------------------------------
391 bool CCombatPhrase::validate()
393 if (_Attacker == NULL )
395 nlwarning("<CCombatPhrase::validate> Found NULL attacker.");
396 return false;
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;
405 return 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;
413 //// TEMP
414 _ExecutionBehaviour = MBEHAV::DEFAULT_ATTACK;
416 _Idle = false;
418 if (_State == Evaluated)
419 _State = Validated;
420 else if (_State == ExecutionInProgress)
421 _State = SecondValidated;
422 else
423 nlwarning("Validate phrase while in state %u", _State );
425 bool engaged = false;
426 string errorCode;
427 CEntityBase *defender = NULL;
429 // get target
430 TDataSetRow targetRowId = TheDataset.getDataSetRow(actingEntity->getTarget());
431 if ( !_Defender || targetRowId != _Defender->getEntityRowId() )
433 createDefender(targetRowId);
436 if (!_Defender)
438 _CurrentTargetIsValid = false;
439 errorCode = "INVALID_TARGET";
441 else
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 )
450 engaged = true;
452 else
454 engaged = false;
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;
466 //if (!engaged)
467 // PHRASE_UTILITIES::sendEngageFailedMessage( TheDataset.getEntityId(_Attacker->getEntityRowId()) );
469 //return false;
471 else
473 // check opening
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;
481 if (combat)
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");
487 return false;
489 if (_Opening == string("Dodge") && event != COMBAT_HISTORY::Dodge)
491 PHRASE_UTILITIES::sendSimpleMessage( _Attacker->getEntityRowId(), "EGS_OPENING_DODGE_FAILED");
492 return false;
495 PHRASE_UTILITIES::sendSimpleMessage( _Attacker->getEntityRowId(), "EGS_OPENING_SUCCESS");
498 _CurrentTargetIsValid = checkTargetValidity( _Defender->getEntityRowId(), errorCode);
502 if (!_CurrentTargetIsValid)
504 // if (!engaged)
505 // PHRASE_UTILITIES::sendEngageFailedMessage(TheDataset.getEntityId(_Attacker->getEntityRowId()));
507 PHRASE_UTILITIES::sendSimpleMessage(_Attacker->getEntityRowId(), errorCode);
508 idle( true );
511 // get the weapon used by the acting entity and check it
512 CCombatWeapon weapon;
513 CCombatWeapon ammo;
514 if ( _Attacker->getItem( CCombatAttacker::RightHandItem, weapon) )
516 if (weapon.Family == ITEMFAMILY::MELEE_WEAPON )
518 _MeleeCombat = true;
520 else if (weapon.Family == ITEMFAMILY::RANGE_WEAPON )
522 _MeleeCombat = false;
523 // test ammo
524 if ( _Attacker->getItem( CCombatAttacker::Ammo, ammo) )
526 // check ammo qty
527 if ( !_Attacker->checkAmmoAmount() )
529 PHRASE_UTILITIES::sendSimpleMessage( _Attacker->getEntityRowId(), "EGS_NOT_ENOUGH_AMMO");
530 _BeingProcessed = false;
531 return false;
534 // lock ammos
535 _Attacker->lockAmmos();
537 else
539 PHRASE_UTILITIES::sendSimpleMessage( _Attacker->getEntityRowId(), "BS_NO_AMMO");
540 _BeingProcessed = false;
541 return false;
544 else
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");
549 // if (!engaged)
550 // PHRASE_UTILITIES::sendEngageFailedMessage( TheDataset.getEntityId(_Attacker->getEntityRowId()) );
552 _BeingProcessed = false;
553 return false;
556 else
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;
566 if (hp <= 0 )
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");
571 // if (!engaged)
572 // PHRASE_UTILITIES::sendEngageFailedMessage( TheDataset.getEntityId(_Attacker->getEntityRowId()) );
574 // _BeingProcessed = false;
575 // return false;
576 idle(true);
577 _CurrentTargetIsValid = false;
580 if(_MeleeCombat)
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;
592 idle( true );
593 INFOLOG("<CCombatPhrase::validate> Entity %s is isn't engaged IDLE mode",TheDataset.getEntityId(_Attacker->getEntityRowId()).toString().c_str());
596 else
598 // test range
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;
609 idle( 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() );
623 return false;
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";
632 return false;
635 if (_MeleeCombat)
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() );
641 return false;
644 else
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;
650 return 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 );
668 _Idle = true;
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() );
675 return false;
679 // set the attacks flag of the acting entity
680 actingEntity->setActionFlag( RYZOMACTIONFLAGS::Attacks, true );
682 _Validated = true;
683 _BeingProcessed = false;
685 return true;
686 } // validate //
688 //--------------------------------------------------------------
689 // update()
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)
700 apply();
702 else if ( _State == Validated || _State == SecondValidated || _State == ExecutionInProgress )
704 _Idle = false;
706 CEntityBase *actor = _Attacker->getEntity();
707 if (!actor)
709 _BeingProcessed = false;
710 // cannot reset the action flag, the actor cannot be found...
711 return false;
714 // check is actor is stunned
715 if (actor->isStunned())
717 _Idle = true;
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);
728 if (!_Defender)
730 _CurrentTargetIsValid =false;
731 _Idle = true;
733 else
735 string errorCode;
736 _CurrentTargetIsValid = checkTargetValidity( _Defender->getEntityRowId(), errorCode);
737 if ( !_CurrentTargetIsValid )
739 PHRASE_UTILITIES::sendSimpleMessage(_Attacker->getEntityRowId(), errorCode);
740 _Idle = true;
742 else
744 if (_MeleeCombat)
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;
750 return false;
753 else
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;
759 return false;
765 else
767 if (!_CurrentTargetIsValid)
768 _Idle = true;
771 // check costs
772 string errorCode;
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 );
785 _Idle = true;
789 // check combat float mode
790 if ( _CurrentTargetIsValid && TheDataset.getEntityId(_Attacker->getEntityRowId()).getType() == RYZOMID::player)
792 if (_MeleeCombat )
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;
803 _Idle = true;
806 else
808 CCombatWeapon weapon;
809 CCombatWeapon ammo;
810 _Attacker->getItem( CCombatAttacker::RightHandItem, weapon);
811 _Attacker->getItem( CCombatAttacker::Ammo, ammo);
813 // test range
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;
824 idle( 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");
835 end();
838 _BeingProcessed = false;
840 return true;
841 } // update //
843 //--------------------------------------------------------------
844 // execute()
845 //--------------------------------------------------------------
846 void CCombatPhrase::execute()
848 if( _Idle || _NbWaitingRequests != 0)
849 return;
851 const NLMISC::TGameCycle time = CTickEventHandler::getGameCycle();
853 _State = CSPhrase::ExecutionInProgress;
855 _ExecutionEndDate = time + _ExecutionLengthModifier ;// + uint32(sentenceLatency / CTickEventHandler::getGameTimeStep()) ;
857 if (_Attacker)
859 CCharacter* player = dynamic_cast<CCharacter*> (_Attacker->getEntity());
860 if (player)
861 player->setCurrentAction(CLIENT_ACTION_TYPE::Combat,_ExecutionEndDate);
863 } // execute //
865 //--------------------------------------------------------------
866 // apply()
867 //--------------------------------------------------------------
868 void CCombatPhrase::apply()
870 if ( !_Attacker || !_Defender)
872 nlwarning("<CCombatPhrase::validate> Found NULL attacker or defender.");
873 return;
876 // spend stamina, hp
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;
882 return;
885 actingEntity->setActionFlag( RYZOMACTIONFLAGS::Attacks, false );
887 CEntityBase *defender = _Defender->getEntity();
888 if (!defender)
890 nlwarning("<CCombatPhrase::apply> Cannot get entity base pointer, error");
891 return;
894 // get combat object
895 CCombat *combat = PhraseManager->getCombatInitiatedBy( _Attacker->getEntityRowId() );
896 if (!combat)
897 return;
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)
920 stamina.Current = 0;
924 if ( _HPCost != 0)
926 actingEntity->changeCurrentHp( (_HPCost) * (-1) );
929 CCombatWeapon weapon;
930 CCombatWeapon ammo;
931 if ( _Attacker->getItem( CCombatAttacker::RightHandItem, weapon) )
933 if (weapon.Family == ITEMFAMILY::MELEE_WEAPON )
935 _MeleeCombat = true;
937 else if (weapon.Family == ITEMFAMILY::RANGE_WEAPON )
939 if ( _Attacker->getItem( CCombatAttacker::Ammo, ammo) )
941 // check ammo qty
942 if ( !_Attacker->checkAmmoAmount() )
944 PHRASE_UTILITIES::sendSimpleMessage( _Attacker->getEntityRowId(), "EGS_NOT_ENOUGH_AMMO");
945 _BeingProcessed = false;
946 return;
949 // lock ammos
950 _Attacker->lockAmmos();
952 else
954 PHRASE_UTILITIES::sendSimpleMessage( _Attacker->getEntityRowId(), "BS_NO_AMMO");
955 _BeingProcessed = false;
956 return;
959 else
961 // ERROR -> Not a weapon
962 PHRASE_UTILITIES::sendSimpleMessage(_Attacker->getEntityRowId(), "BS_ITEM_INCOMPATIBLE");
963 _BeingProcessed = false;
964 return;
967 weapon.SkillValue = _Attacker->getSkillValue(weapon.Skill);
969 else
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);
979 // TODO
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
991 sint32 damage = 0;
992 if (!testPhraseSuccess())
994 // Total failure
995 // send miss message
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)
1003 // failed
1004 combat->pushEvent( COMBAT_HISTORY::Miss );
1005 PHRASE_UTILITIES::sendCombatResistMessages( _Attacker->getEntityRowId(), _Defender->getEntityRowId() );
1007 else if (_PhraseSuccessDamageFactor < 0.0f)
1009 // fumble
1010 combat->pushEvent( COMBAT_HISTORY::Fumble );
1011 PHRASE_UTILITIES::sendFumbleMessage( _Attacker->getEntityRowId(), _Defender->getEntityRowId() );
1012 // double latency !
1013 _LatencyEndDate = (_LatencyEndDate-time) * 2 + time;
1016 else
1018 // get shield
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 );
1027 else
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 );
1044 else
1045 combat->pushEvent( COMBAT_HISTORY::Parry );
1047 else
1049 if (_PhraseSuccessDamageFactor > 1.0f)
1051 // critical strike
1052 PHRASE_UTILITIES::sendCriticalHitMessage( _Attacker->getEntityRowId(), _Defender->getEntityRowId() );
1055 // Compute damage
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;
1064 else
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 );
1076 if ( effect )
1077 damage *= effect->getParamValue() / 100;
1079 // armor
1080 CCombatArmor armor;
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;
1103 break;
1104 case ARMORTYPE::MEDIUM:
1105 armorAbsorptionFactor = _MediumArmorAbsorptionMultiplier;
1106 armorAbsorptionModifier = _MediumArmorAbsorptionModifier;
1107 break;
1108 case ARMORTYPE::HEAVY:
1109 armorAbsorptionFactor = _HeavyArmorAbsorptionMultiplier;
1110 armorAbsorptionModifier = _HeavyArmorAbsorptionModifier;
1111 break;
1112 default:;
1115 switch(shield.ArmorType)
1117 case ARMORTYPE::LIGHT:
1118 shieldAbsorptionFactor = _LightArmorAbsorptionMultiplier;
1119 shieldAbsorptionModifier = _LightArmorAbsorptionModifier;
1120 break;
1121 case ARMORTYPE::MEDIUM:
1122 shieldAbsorptionFactor = _MediumArmorAbsorptionMultiplier;
1123 shieldAbsorptionModifier = _MediumArmorAbsorptionModifier;
1124 break;
1125 case ARMORTYPE::HEAVY:
1126 shieldAbsorptionFactor = _HeavyArmorAbsorptionMultiplier;
1127 shieldAbsorptionModifier = _HeavyArmorAbsorptionModifier;
1128 break;
1129 default:;
1132 switch( dmgType)
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;
1146 break;
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;
1159 break;
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;
1172 break;
1173 default:
1174 break;
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() );
1197 if (damage == 0)
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);
1203 nlwarning("");
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);
1210 // apply damage
1211 if (damage > 0)
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 );
1229 else
1231 CPhraseManager::getInstance()->breakCast(attackerLevel,actingEntity,defender);
1234 // add modifier to sentence AI event reports
1235 // TODO
1237 // test spell break
1238 //PHRASE_UTILITIES::testSpellBreakOnDamage( _Defender->getEntityRowId(), _Attacker->getEntityRowId(), damage, damageType);
1239 // TODO
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;
1249 //TEMPFIX
1250 // clip score to 0
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);
1257 if ( lostSap != 0 )
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;
1263 //TEMPFIX
1264 // clip score to 0
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) );
1283 else
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 );
1300 // compute aggro
1301 sint32 aggro = 0;
1302 const sint32 maxPv = defender->getPhysScores()._PhysicalScores[SCORES::hit_points].Max;
1303 if (maxPv)
1305 aggro = (-1) * sint32((100.0 * double(damage + _AggroModifier))/double(maxPv) * _AggroMultiplier) ;
1308 // update the repor
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());
1316 if (player)
1317 player->setCurrentAction(CLIENT_ACTION_TYPE::Combat,_LatencyEndDate);
1319 actingEntity->setActionFlag( RYZOMACTIONFLAGS::Attacks, false );
1321 _BeingProcessed = false;
1322 } // apply //
1324 //--------------------------------------------------------------
1325 // end()
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());
1337 if (entityPtr)
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);
1350 if (player)
1351 player->clearCurrentAction();
1352 } // end //
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;
1370 else
1372 if ( _RootSkill == SKILLS::unknown )
1373 //TODO barehandedcombat
1374 _AttackSkill = SKILLS::SFM1H;//SKILLS::BareHandCombat;
1375 else
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);
1383 sint32 malus = 0;
1384 sint32 nb = aggressors.size();
1385 if (nb)
1386 malus = (1-nb)*10;
1388 sint32 valueTemp = _Defender->getDefenseValue();
1389 sint32 defenderLevel = max( sint32((valueTemp + malus ) / 10), (sint32)0);
1391 _DeltaLevel = attackerLevel - defenderLevel;
1393 // oposition test
1394 uint8 chances = PHRASE_UTILITIES::getSuccessChance( defenderLevel - attackerLevel );
1396 // TEMP HACK : limit dodge chances for the moment, wait new rules (more 'fun')
1397 chances /= 3;
1400 float result = PHRASE_UTILITIES::getSucessFactor(chances, (uint8)RandomGenerator.rand(99) );
1402 if (result>=1.0f)
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());
1409 else
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());
1415 return true;
1418 return false;
1419 } // testOpponentDefense //
1422 //--------------------------------------------------------------
1423 // testPhraseSuccess()
1424 //--------------------------------------------------------------
1425 bool CCombatPhrase::testPhraseSuccess()
1427 sint32 skillValue;
1428 if ( _RootSkill != SKILLS::unknown )
1429 skillValue = _Attacker->getSkillValue( _RootSkill );
1430 else
1432 //TODO skill barehanded combat
1433 CCombatWeapon weapon;
1434 if (_Attacker->getItem( CCombatAttacker::RightHandItem, weapon) )
1436 skillValue = weapon.SkillValue;
1438 else
1440 //skillValue = _Attacker->getSkillValue( SKILLS::BareHandCombat );
1441 skillValue = _Attacker->getSkillValue( SKILLS::SFM1H );
1445 const sint16 relativeLevel = (skillValue + _SabrinaCredit - 2*_SabrinaCost) / 10;
1447 // oposition test
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)
1455 // failed
1456 return false;
1458 else if (_PhraseSuccessDamageFactor < 0.0f)
1460 // fumble
1461 return false;
1464 return true;
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";
1476 return false;
1479 if (targetRowId == _Attacker->getEntityRowId() && TheDataset.getEntityId(_Attacker->getEntityRowId()).getType() == RYZOMID::player)
1481 errorCode = "BS_INVALID_TARGET";
1482 return false;
1485 if ( ! PHRASE_UTILITIES::testOffensiveActionAllowed(_Attacker->getEntityRowId(), targetRowId, errorCode) )
1487 return false;
1490 return true;
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)
1503 return true;
1505 const SCharacteristicsAndScores &stamina = _Attacker->getEntity()->getScores()._PhysicalScores[SCORES::stamina];
1506 if ( stamina.Current < _StaminaCost)
1508 errorCode = "EGS_TOO_EXPENSIVE_STAMINA";
1509 return false;
1512 const SCharacteristicsAndScores &hp = _Attacker->getEntity()->getScores()._PhysicalScores[SCORES::hit_points];
1513 if ( hp.Current < _HPCost)
1515 errorCode = "EGS_TOO_EXPENSIVE_HP";
1516 return false;
1519 return true;
1520 } // checkPhraseCost //
1523 //--------------------------------------------------------------
1524 // createDefender()
1525 //--------------------------------------------------------------
1526 void CCombatPhrase::createDefender( const TDataSetRow &targetRowId )
1528 if (_Defender != NULL)
1530 delete _Defender;
1531 _Defender = NULL;
1534 if ( !targetRowId.isValid() || !TheDataset.isDataSetRowStillValid(targetRowId) )
1535 return;
1537 // create defender structure
1538 if ( TheDataset.getEntityId(targetRowId).getType() == RYZOMID::player )
1540 _Defender = new CCombatDefenderPlayer(targetRowId);
1542 else
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))
1560 return false;
1564 return true;
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 //