// Ryzom - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . ////////////// // INCLUDE // ////////////// #include "stdpch.h" #include "combat_phrase.h" // net #include "nel/net/message.h" // misc #include "nel/misc/common.h" // game share #include "game_share/msg_combat_move_service.h" #include "game_share/mode_and_behaviour.h" #include "game_share/combat_state.h" //#include "game_share/sheath.h" #include "game_share/entity_structure/statistic.h" #include "phrase_utilities_functions.h" #include "entity_base.h" #include "character.h" #include "egs_mirror.h" #include "phrase_manager.h" #include "player_manager.h" #include "s_phrase_factory.h" #include "combat_action_stun.h" ////////////// // USING // ////////////// using namespace std; using namespace NLNET; using namespace NLMISC; using namespace RY_GAME_SHARE; ////////////// // EXTERN // ////////////// extern CRandom RandomGenerator; extern CPhraseManager *PhraseManager; extern CPlayerManager PlayerManager; uint16 HandToHandDamage = 20*260/11; uint16 HandToHandSpeed = 30; // 3s = 30 ticks DEFAULT_SPHRASE_FACTORY( CCombatPhrase, BRICK_TYPE::COMBAT ); /* New = 0, Evaluated, Validated, Idle, ExecutionInProgress, SecondValidated, WaitNextCycle, Latent, LatencyEnded, UnknownState, */ bool CCombatPhrase::build( const TDataSetRow & actorRowId, const std::vector< const CStaticBrick* >& bricks ) { if (actorRowId.isValid() && TheDataset.isDataSetRowStillValid(actorRowId) ) { // create attacker structure CEntityBase *attacker = PHRASE_UTILITIES::entityPtrFromId(actorRowId); if (attacker == NULL) return false; if (attacker->getId().getType() == RYZOMID::player ) { _Attacker = new CCombatAttackerPlayer(actorRowId); if (!_Attacker) return false; } else { _Attacker = new CCombatAttackerAI(actorRowId); if (!_Attacker) return false; } } // create defender structure -> wait first validation // add bricks for (uint i = 0; i < bricks.size(); i++) { addBrick( *bricks[i] ); } return true; } //-------------------------------------------------------------- // destructor //-------------------------------------------------------------- CCombatPhrase::~CCombatPhrase() { if (_Attacker != NULL) delete _Attacker; if (_Defender != NULL) delete _Defender; const uint size = _CombatActions.size(); for (uint i = 0 ; i < size ; ++i) { if (_CombatActions[i] != NULL) delete _CombatActions[i]; } }; //-------------------------------------------------------------- // constructor //-------------------------------------------------------------- CCombatPhrase::CCombatPhrase(const CStaticBrick &rootBrick) { init(); //_RootSkill = rootBrick.Skill; addBrick(rootBrick); } // constructor // //-------------------------------------------------------------- // init() //-------------------------------------------------------------- void CCombatPhrase::init() { _State = CSPhrase::New; _ExecutionBehaviour = MBEHAV::UNKNOWN_BEHAVIOUR; _SuccessBehaviour = MBEHAV::UNKNOWN_BEHAVIOUR; _CriticalSuccessBehaviour = MBEHAV::UNKNOWN_BEHAVIOUR; _FailureBehaviour = MBEHAV::UNKNOWN_BEHAVIOUR; _FumbleBehaviour = MBEHAV::UNKNOWN_BEHAVIOUR; _EndBehaviour = MBEHAV::UNKNOWN_BEHAVIOUR; _StopBehaviour = MBEHAV::UNKNOWN_BEHAVIOUR; _ForcedLocalisation = SLOT_EQUIPMENT::UNDEFINED; _Attacker = NULL; _Defender = NULL; _RootSkill = SKILLS::unknown; _AttackSkill = SKILLS::unknown; _CyclicPhrase = false; _Idle = false; _TargetTooFarMsg = false; _NotEnoughStaminaMsg = false; _NotEnoughHpMsg = false; _CurrentTargetIsValid = false; _MeleeCombat = true; _ExecutionEndDate = 0; _LatencyEndDate = 0; _NbWaitingRequests = 0; _SabrinaCost = 0; _SabrinaCredit = 0; _HPCost = 0; _StaminaCost = 0; _AttackSkillModifier = 0; _ExecutionLengthModifier = 0; _HitRateModifier = 0; _DamageModifier = 0; _DeltaLevel = 0; _PhraseSuccessDamageFactor = 1.0f; _LightArmorAbsorptionMultiplier = 1.0f; _LightArmorWearMultiplier = 1.0f; _MediumArmorAbsorptionMultiplier= 1.0f; _MediumArmorWearMultiplier = 1.0f; _HeavyArmorAbsorptionMultiplier = 1.0f; _HeavyArmorWearMultiplier = 1.0f; _LightArmorAbsorptionModifier = 0; _MediumArmorAbsorptionModifier = 0; _HeavyArmorAbsorptionModifier = 0; _AggroMultiplier = 1.0f; _AggroModifier = 0; _DamageFactor = 1.0f; _StaminaLossFactor = 0.0f; _StaminaLossModifier = 0; _SapLossFactor = 0.0f; _SapLossModifier = 0; _DamagePointBlank = 1.0f; _DamageShortRange = 1.0f; _DamageMediumRange = 1.0f; _DamageLongRange = 1.0f; } // init // //-------------------------------------------------------------- // addBrick() //-------------------------------------------------------------- void CCombatPhrase::addBrick( const CStaticBrick &brick ) { if ( brick.ForcedLocalisation != SLOT_EQUIPMENT::UNDEFINED ) { if ( _ForcedLocalisation == SLOT_EQUIPMENT::UNDEFINED ) { _ForcedLocalisation = brick.ForcedLocalisation; } else if (brick.ForcedLocalisation != _ForcedLocalisation) { nlwarning(" 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()); } } brick.SabrinaValue < 0 ? _SabrinaCredit += abs(brick.SabrinaValue) : _SabrinaCost += brick.SabrinaValue; // process params unsigned i; for (i=0 ; iid()) { case TBrickParam::HP: // INFOLOG("HP: %i",((CSBrickParamHp *)brick.Params[i])->Hp); _HPCost += ((CSBrickParamHp *)brick.Params[i])->Hp; break; case TBrickParam::SAP: //printf("SAP: %i\n",((CSBrickParamSap *)brick.Params[i])->Sap); break; case TBrickParam::STA: // INFOLOG("STA: %i",((CSBrickParamSta *)brick.Params[i])->Sta); _StaminaCost += ((CSBrickParamSta *)brick.Params[i])->Sta; break; case TBrickParam::EXECUTION_LENGTH: // INFOLOG("EXECUTION_LENGTH: %i",((CSBrickParamExecutionLength *)brick.Params[i])->ExecutionLength); _ExecutionLengthModifier += ((CSBrickParamExecutionLength *)brick.Params[i])->ExecutionLength; break; case TBrickParam::LATENCY_LENGTH: // INFOLOG("LATENCY_LENGTH: %i",((CSBrickParamLatencyLength *)brick.Params[i])->LatencyLength); _HitRateModifier += ((CSBrickParamLatencyLength *)brick.Params[i])->LatencyLength; break; case TBrickParam::DMG_MOD: // INFOLOG("DMG_MOD: %i",((CSBrickParamDamageModifier *)brick.Params[i])->DamageModifier); _DamageModifier += ((CSBrickParamDamageModifier *)brick.Params[i])->DamageModifier; break; case TBrickParam::DMG_MUL: // INFOLOG("DMG_MUL: %i",((CSBrickParamDamageFactor *)brick.Params[i])->DamageFactor); _DamageFactor += ((CSBrickParamDamageFactor *)brick.Params[i])->DamageFactor - 1.0f; break; case TBrickParam::AGGRO: // INFOLOG("AGGRO: factor %f mod %d",((CSBrickParamAggro *)brick.Params[i])->AggroFactor, ((CSBrickParamAggro *)brick.Params[i])->AggroModifier); _AggroMultiplier += ((CSBrickParamAggro *)brick.Params[i])->AggroFactor - 1.0f; _AggroModifier += ((CSBrickParamAggro *)brick.Params[i])->AggroModifier; break; case TBrickParam::STA_LOSS: // INFOLOG("STA_LOSS: %f, %u",((CSBrickParamStaLossFactor *)brick.Params[i])->StaLossFactor, ((CSBrickParamStaLossFactor *)brick.Params[i])->StaLossModifier); _StaminaLossFactor += ((CSBrickParamStaLossFactor *)brick.Params[i])->StaLossFactor; _StaminaLossModifier += ((CSBrickParamStaLossFactor *)brick.Params[i])->StaLossModifier; break; case TBrickParam::SAP_LOSS: // INFOLOG("SAP_LOSS: %f, %u",((CSBrickParamSapLossFactor *)brick.Params[i])->SapLossFactor, ((CSBrickParamSapLossFactor *)brick.Params[i])->SapLossModifier); _SapLossFactor += ((CSBrickParamSapLossFactor *)brick.Params[i])->SapLossFactor; _SapLossModifier += ((CSBrickParamSapLossFactor *)brick.Params[i])->SapLossModifier; break; case TBrickParam::ATT_SKILL_MOD: // INFOLOG("ATT_SKILL_MOD: %f",((CSBrickParamAttackSkillModifier *)brick.Params[i])->AttackSkillModifier); _AttackSkillModifier += ((CSBrickParamAttackSkillModifier *)brick.Params[i])->AttackSkillModifier; break; case TBrickParam::AIM: // INFOLOG("AIMED SLOT: %s",((CSBrickParamAim *)brick.Params[i])->AimedSlot.c_str()); _ForcedLocalisation = SLOT_EQUIPMENT::stringToSlotEquipment( ((CSBrickParamAim *)brick.Params[i])->AimedSlot ); break; case TBrickParam::OPENING: INFOLOG("OPENING : %s",((CSBrickParamOpening *)brick.Params[i])->OpeningType.c_str()); _Opening = ((CSBrickParamOpening *)brick.Params[i])->OpeningType; break; case TBrickParam::BREAK_CAST: // nlinfo("TODO TODO BREAK_CAST : modifier on damage for break cast test %d",((CSBrickParamBreakCast *)brick.Params[i])->DamageModifier); // $*STRUCT CSBrickParamBreakCast: public TBrickParam::CId // $*-i sint32 DamageModifier = 0 // damage modifier only for break cast test break; case TBrickParam::RANGES: /*printf("RANGES: %f", ((CSBrickParamRanges *)brick.Params[i])->ShortRange); printf(" : %f", ((CSBrickParamRanges *)brick.Params[i])->MediumRange); printf(" : %f\n", ((CSBrickParamRanges *)brick.Params[i])->LongRange); */ break; case TBrickParam::COMBAT_STUN: { // $*STRUCT CSBrickParamCombatStun: public TBrickParam::CId // $*-f float Duration // duration of the stun in seconds // $*-f float DurationResisted // duration of the stun in seconds when resisted // $*-i uint16 Power // stun power (to oppose to target resistance) // 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); NLMISC::TGameCycle duration = NLMISC::TGameCycle( ((CSBrickParamCombatStun *)brick.Params[i])->Duration / CTickEventHandler::getGameTimeStep() ); NLMISC::TGameCycle durationResisted = NLMISC::TGameCycle( ((CSBrickParamCombatStun *)brick.Params[i])->DurationResisted / CTickEventHandler::getGameTimeStep() ); uint16 power = ((CSBrickParamCombatStun *)brick.Params[i])->Power; _CombatActions.push_back( new CCombatActionStun( _Attacker->getEntityRowId(), this, duration, durationResisted, power)); } break; case TBrickParam::LARMOR_MOD: // INFOLOG("LARMOR_MOD: %f, %u",((CSBrickParamLightArmorMod *)brick.Params[i])->AborptionFactor, ((CSBrickParamLightArmorMod *)brick.Params[i])->AborptionModifier); _LightArmorAbsorptionMultiplier += ((CSBrickParamLightArmorMod *)brick.Params[i])->AborptionFactor; _LightArmorAbsorptionModifier += ((CSBrickParamLightArmorMod *)brick.Params[i])->AborptionModifier; break; case TBrickParam::MARMOR_MOD: // INFOLOG("MARMOR_MOD: %f, %u",((CSBrickParamMediumArmorMod *)brick.Params[i])->AborptionFactor, ((CSBrickParamMediumArmorMod *)brick.Params[i])->AborptionModifier); _MediumArmorAbsorptionMultiplier += ((CSBrickParamMediumArmorMod *)brick.Params[i])->AborptionFactor; _MediumArmorAbsorptionModifier += ((CSBrickParamMediumArmorMod *)brick.Params[i])->AborptionModifier; break; case TBrickParam::HARMOR_MOD: // INFOLOG("HARMOR_MOD: %f, %u",((CSBrickParamHeavyArmorMod *)brick.Params[i])->AborptionFactor, ((CSBrickParamHeavyArmorMod *)brick.Params[i])->AborptionModifier); _HeavyArmorAbsorptionMultiplier += ((CSBrickParamHeavyArmorMod *)brick.Params[i])->AborptionFactor; _HeavyArmorAbsorptionModifier += ((CSBrickParamHeavyArmorMod *)brick.Params[i])->AborptionModifier; break; default: break; } } } // addBrick // //-------------------------------------------------------------- // evaluate() //-------------------------------------------------------------- bool CCombatPhrase::evaluate(CEvalReturnInfos *msg) { _State = CSPhrase::Evaluated; _Idle = false; _PhraseSuccessDamageFactor = 0.0f; _AttackSkill = SKILLS::unknown; _Validated = false; _TargetTooFarMsg = false; _NotEnoughStaminaMsg = false; _NotEnoughHpMsg = false; _DisengageOnEnd = false; //_CurrentTargetIsValid = false; //_MeleeCombat = true; return true; } // evaluate // //-------------------------------------------------------------- // validate() //-------------------------------------------------------------- bool CCombatPhrase::validate() { if (_Attacker == NULL ) { nlwarning(" Found NULL attacker."); return false; } CEntityBase *actingEntity = _Attacker->getEntity(); if (actingEntity == NULL) { nlwarning(" Cannot find entity ptr for acting entity %u", _Attacker->getEntityRowId().getIndex()); _BeingProcessed = false; return false; } // set the attack flag to 0, will be set to 1 at the end of method if phrase is valid actingEntity->setActionFlag( RYZOMACTIONFLAGS::Attacks, false ); _BeingProcessed = true; //// TEMP _ExecutionBehaviour = MBEHAV::DEFAULT_ATTACK; _Idle = false; if (_State == Evaluated) _State = Validated; else if (_State == ExecutionInProgress) _State = SecondValidated; else nlwarning("Validate phrase while in state %u", _State ); bool engaged = false; string errorCode; CEntityBase *defender = NULL; // get target TDataSetRow targetRowId = TheDataset.getDataSetRow(actingEntity->getTarget()); if ( !_Defender || targetRowId != _Defender->getEntityRowId() ) { createDefender(targetRowId); } if (!_Defender) { _CurrentTargetIsValid = false; errorCode = "INVALID_TARGET"; } else { // check the player has engaged a target if not auto engage the target TDataSetRow entityRowId = PhraseManager->getEntityEngagedMeleeBy( _Attacker->getEntityRowId() ); if (entityRowId.isValid() && TheDataset.isDataSetRowStillValid(entityRowId) ) { CEntityId entityId = TheDataset.getEntityId(entityRowId); if ( _Defender->getEntityRowId() == entityRowId ) { engaged = true; } else { engaged = false; } } defender = _Defender->getEntity(); if (defender == NULL) { _CurrentTargetIsValid = false; errorCode = "INVALID_TARGET"; //nlwarning(" Cannot find entity ptr for defending entity %u", _Defender->getEntityRowId().getIndex()); //_BeingProcessed = false; //if (!engaged) // PHRASE_UTILITIES::sendEngageFailedMessage( TheDataset.getEntityId(_Attacker->getEntityRowId()) ); //return false; } else { // check opening if ( !_Opening.empty() && !_Validated) { // get combat initiated by current defender CCombat *combat = PhraseManager->getCombatInitiatedBy( _Defender->getEntityRowId() ); COMBAT_HISTORY::TCombatHistory event = COMBAT_HISTORY::Unknown; if (combat) COMBAT_HISTORY::TCombatHistory event = combat->historyEvent(0); if (_Opening == string("Parry") && event != COMBAT_HISTORY::Parry) { PHRASE_UTILITIES::sendSimpleMessage( _Attacker->getEntityRowId(), "EGS_OPENING_PARRY_FAILED"); return false; } if (_Opening == string("Dodge") && event != COMBAT_HISTORY::Dodge) { PHRASE_UTILITIES::sendSimpleMessage( _Attacker->getEntityRowId(), "EGS_OPENING_DODGE_FAILED"); return false; } PHRASE_UTILITIES::sendSimpleMessage( _Attacker->getEntityRowId(), "EGS_OPENING_SUCCESS"); } _CurrentTargetIsValid = checkTargetValidity( _Defender->getEntityRowId(), errorCode); } } if (!_CurrentTargetIsValid) { // if (!engaged) // PHRASE_UTILITIES::sendEngageFailedMessage(TheDataset.getEntityId(_Attacker->getEntityRowId())); PHRASE_UTILITIES::sendSimpleMessage(_Attacker->getEntityRowId(), errorCode); idle( true ); } // get the weapon used by the acting entity and check it CCombatWeapon weapon; CCombatWeapon ammo; if ( _Attacker->getItem( CCombatAttacker::RightHandItem, weapon) ) { if (weapon.Family == ITEMFAMILY::MELEE_WEAPON ) { _MeleeCombat = true; } else if (weapon.Family == ITEMFAMILY::RANGE_WEAPON ) { _MeleeCombat = false; // test ammo if ( _Attacker->getItem( CCombatAttacker::Ammo, ammo) ) { // check ammo qty if ( !_Attacker->checkAmmoAmount() ) { PHRASE_UTILITIES::sendSimpleMessage( _Attacker->getEntityRowId(), "EGS_NOT_ENOUGH_AMMO"); _BeingProcessed = false; return false; } // lock ammos _Attacker->lockAmmos(); } else { PHRASE_UTILITIES::sendSimpleMessage( _Attacker->getEntityRowId(), "BS_NO_AMMO"); _BeingProcessed = false; return false; } } else { DEBUGLOG(" Entity %u, item in right hand is not a weapon", _Attacker->getEntityRowId().getIndex() ); // ERROR -> Not a weapon PHRASE_UTILITIES::sendSimpleMessage(_Attacker->getEntityRowId(), "BS_ITEM_INCOMPATIBLE"); // if (!engaged) // PHRASE_UTILITIES::sendEngageFailedMessage( TheDataset.getEntityId(_Attacker->getEntityRowId()) ); _BeingProcessed = false; return false; } } else { _MeleeCombat = true; // hand to hand } if ( _CurrentTargetIsValid && defender != 0 && TheDataset.getEntityId(_Attacker->getEntityRowId()).getType() == RYZOMID::player) { // check target is still alive // test the targeted entity is still alive const sint32 hp = defender->getScores()._PhysicalScores[ SCORES::hit_points ].Current; if (hp <= 0 ) { nlwarning(" Entity %s is dead", TheDataset.getEntityId(_Defender->getEntityRowId()).toString().c_str()); //errorCode = "BS_TARGET_DEAD"; PHRASE_UTILITIES::sendSimpleMessage(_Attacker->getEntityRowId(), "BS_TARGET_DEAD"); // if (!engaged) // PHRASE_UTILITIES::sendEngageFailedMessage( TheDataset.getEntityId(_Attacker->getEntityRowId()) ); // _BeingProcessed = false; // return false; idle(true); _CurrentTargetIsValid = false; } if(_MeleeCombat) { // check combat float mode CCharacter *character = PlayerManager.getChar(_Attacker->getEntityRowId()); if (character && !character->getCombatFloatMode()) { if (!_TargetTooFarMsg) { PHRASE_UTILITIES::sendSimpleMessage( _Attacker->getEntityRowId(), "BS_TARGET_TOO_FAR_OR"); _TargetTooFarMsg = true; } idle( true ); INFOLOG(" Entity %s is isn't engaged IDLE mode",TheDataset.getEntityId(_Attacker->getEntityRowId()).toString().c_str()); } } else { // test range double range = weapon.Range + ammo.Range; double distance = PHRASE_UTILITIES::getDistance(_Attacker->getEntityRowId(), defender->getEntityRowId()); if ( distance > range) { if (!_TargetTooFarMsg) { PHRASE_UTILITIES::sendSimpleMessage( _Attacker->getEntityRowId(), "BS_TARGET_TOO_FAR"); _TargetTooFarMsg = true; } idle( true ); INFOLOG(" Entity %s is too far from it's target %s",TheDataset.getEntityId(_Attacker->getEntityRowId()).toString().c_str(), defender->getId().toString().c_str()); } } } // check the player has engaged a target if not auto engage the target if (!engaged && _CurrentTargetIsValid) { // Test entity can engage combat right now /* if ( ! PhraseManager->canEntityEngageCombat(TheDataset.getEntityId(_Attacker->getEntityRowId())) ) { //errorCode = "EGS_CANNOT_ENGAGE_COMBAT_YET"; nlwarning(" Entity %s cannot engage combat yet", TheDataset.getEntityId(_Attacker->getEntityRowId()).toString().c_str() ); return false; } */ // engage the target and continue /* const double d = PHRASE_UTILITIES::getDistance( TheDataset.getEntityId(_Attacker->getEntityRowId()), TheDataset.getEntityId(_Defender->getEntityRowId()) ); if (d >= MaxEngageMeleeDistance ) { PHRASE_UTILITIES::sendEngageFailedMessage(TheDataset.getEntityId(_Attacker->getEntityRowId())); //errorCode = "BS_TARGET_TOO_FAR"; return false; } */ if (_MeleeCombat) { if ( ! PHRASE_UTILITIES::engageTargetInMelee( TheDataset.getEntityId(_Attacker->getEntityRowId()), TheDataset.getEntityId(_Defender->getEntityRowId()) ) ) { _BeingProcessed = false; DEBUGLOG(" Entity %u Failed to engage its target in melee combat", _Attacker->getEntityRowId().getIndex() ); return false; } } else { if ( ! PHRASE_UTILITIES::engageTargetRange( TheDataset.getEntityId(_Attacker->getEntityRowId()), TheDataset.getEntityId(_Defender->getEntityRowId()) ) ) { DEBUGLOG(" Entity %u Failed to engage its target in range combat", _Attacker->getEntityRowId().getIndex() ); _BeingProcessed = false; return false; } } } if(!checkPhraseCost(errorCode)) { if (!_NotEnoughStaminaMsg && errorCode == "EGS_TOO_EXPENSIVE_STAMINA") { _NotEnoughStaminaMsg = true; PHRASE_UTILITIES::sendSimpleMessage( _Attacker->getEntityRowId(), errorCode ); } else if (!_NotEnoughHpMsg && errorCode == "EGS_TOO_EXPENSIVE_HP") { _NotEnoughHpMsg = true; PHRASE_UTILITIES::sendSimpleMessage( _Attacker->getEntityRowId(), errorCode ); } _Idle = true; } if (!validateCombatActions(errorCode)) { PHRASE_UTILITIES::sendSimpleMessage( _Attacker->getEntityRowId(), errorCode ); DEBUGLOG(" Entity %u Failed to validate combat actions, error = %s", _Attacker->getEntityRowId().getIndex(), errorCode.c_str() ); return false; } // set the attacks flag of the acting entity actingEntity->setActionFlag( RYZOMACTIONFLAGS::Attacks, true ); _Validated = true; _BeingProcessed = false; return true; } // validate // //-------------------------------------------------------------- // update() //-------------------------------------------------------------- bool CCombatPhrase::update() { _BeingProcessed = true; const NLMISC::TGameCycle time = CTickEventHandler::getGameCycle(); // if the sentence execution delay time has ended, apply sentence effects if ( _State == SecondValidated && _ExecutionEndDate <= time && _NbWaitingRequests == 0 && !_Idle) { apply(); } else if ( _State == Validated || _State == SecondValidated || _State == ExecutionInProgress ) { _Idle = false; CEntityBase *actor = _Attacker->getEntity(); if (!actor) { _BeingProcessed = false; // cannot reset the action flag, the actor cannot be found... return false; } // check is actor is stunned if (actor->isStunned()) { _Idle = true; } actor->setActionFlag( RYZOMACTIONFLAGS::Attacks, false ); // check target validity TDataSetRow target = TheDataset.getDataSetRow(actor->getTarget()); if (!_Defender || !TheDataset.isDataSetRowStillValid(_Defender->getEntityRowId()) || target != _Defender->getEntityRowId()) { createDefender(target); if (!_Defender) { _CurrentTargetIsValid =false; _Idle = true; } else { string errorCode; _CurrentTargetIsValid = checkTargetValidity( _Defender->getEntityRowId(), errorCode); if ( !_CurrentTargetIsValid ) { PHRASE_UTILITIES::sendSimpleMessage(_Attacker->getEntityRowId(), errorCode); _Idle = true; } else { if (_MeleeCombat) { if ( ! PHRASE_UTILITIES::engageTargetInMelee( TheDataset.getEntityId(_Attacker->getEntityRowId()), TheDataset.getEntityId(_Defender->getEntityRowId()) ) ) { DEBUGLOG(" Entity %u Failed to engage its target in melee combat", _Attacker->getEntityRowId().getIndex() ); _BeingProcessed = false; return false; } } else { if ( ! PHRASE_UTILITIES::engageTargetRange( TheDataset.getEntityId(_Attacker->getEntityRowId()), TheDataset.getEntityId(_Defender->getEntityRowId()) ) ) { DEBUGLOG(" Entity %u Failed to engage its target in range combat", _Attacker->getEntityRowId().getIndex() ); _BeingProcessed = false; return false; } } } } } else { if (!_CurrentTargetIsValid) _Idle = true; } // check costs string errorCode; if(!checkPhraseCost(errorCode)) { if (!_NotEnoughStaminaMsg && errorCode == "EGS_TOO_EXPENSIVE_STAMINA" && !_Idle ) { _NotEnoughStaminaMsg = true; PHRASE_UTILITIES::sendSimpleMessage( _Attacker->getEntityRowId(), errorCode ); } else if (!_NotEnoughHpMsg && errorCode == "EGS_TOO_EXPENSIVE_HP" && !_Idle ) { _NotEnoughHpMsg = true; PHRASE_UTILITIES::sendSimpleMessage( _Attacker->getEntityRowId(), errorCode ); } _Idle = true; } // check combat float mode if ( _CurrentTargetIsValid && TheDataset.getEntityId(_Attacker->getEntityRowId()).getType() == RYZOMID::player) { if (_MeleeCombat ) { CCharacter *character = dynamic_cast (actor); if (character && !character->getCombatFloatMode()) { if (!_TargetTooFarMsg && !_Idle) { PHRASE_UTILITIES::sendSimpleMessage( _Attacker->getEntityRowId(), "BS_TARGET_TOO_FAR_OR"); _TargetTooFarMsg = true; } _Idle = true; } } else { CCombatWeapon weapon; CCombatWeapon ammo; _Attacker->getItem( CCombatAttacker::RightHandItem, weapon); _Attacker->getItem( CCombatAttacker::Ammo, ammo); // test range double range = weapon.Range + ammo.Range; double distance = PHRASE_UTILITIES::getDistance(_Attacker->getEntityRowId(), _Defender->getEntityRowId()); if ( distance > range) { if (!_TargetTooFarMsg) { PHRASE_UTILITIES::sendSimpleMessage( _Attacker->getEntityRowId(), "BS_TARGET_TOO_FAR"); _TargetTooFarMsg = true; } idle( true ); INFOLOG(" Entity %s is too far from it's target %s",TheDataset.getEntityId(_Attacker->getEntityRowId()).toString().c_str(), _Defender->getEntity()->getId().toString().c_str()); } } } actor->setActionFlag( RYZOMACTIONFLAGS::Attacks, true ); } else if ( _State == Latent && _LatencyEndDate <= time ) { INFOLOG("Latency ended"); end(); } _BeingProcessed = false; return true; } // update // //-------------------------------------------------------------- // execute() //-------------------------------------------------------------- void CCombatPhrase::execute() { if( _Idle || _NbWaitingRequests != 0) return; const NLMISC::TGameCycle time = CTickEventHandler::getGameCycle(); _State = CSPhrase::ExecutionInProgress; _ExecutionEndDate = time + _ExecutionLengthModifier ;// + uint32(sentenceLatency / CTickEventHandler::getGameTimeStep()) ; if (_Attacker) { CCharacter* player = dynamic_cast (_Attacker->getEntity()); if (player) player->setCurrentAction(CLIENT_ACTION_TYPE::Combat,_ExecutionEndDate); } } // execute // //-------------------------------------------------------------- // apply() //-------------------------------------------------------------- void CCombatPhrase::apply() { if ( !_Attacker || !_Defender) { nlwarning(" Found NULL attacker or defender."); return; } // spend stamina, hp CEntityBase* actingEntity = PHRASE_UTILITIES::entityPtrFromId( _Attacker->getEntityRowId() ); if (actingEntity == NULL) { nlwarning(" Invalid entity Id %s", TheDataset.getEntityId(_Attacker->getEntityRowId()).toString().c_str() ); _BeingProcessed = false; return; } actingEntity->setActionFlag( RYZOMACTIONFLAGS::Attacks, false ); CEntityBase *defender = _Defender->getEntity(); if (!defender) { nlwarning(" Cannot get entity base pointer, error"); return; } // get combat object CCombat *combat = PhraseManager->getCombatInitiatedBy( _Attacker->getEntityRowId() ); if (!combat) return; _BeingProcessed = true; _TargetTooFarMsg = false; _State = CSPhrase::Latent; _ExecutionBehaviour.Data = 0; _AiEventReport.init(); _AiEventReport.Originator = _Attacker->getEntityRowId(); _AiEventReport.Target = _Defender->getEntityRowId(); _AiEventReport.Type = ACTNATURE::OFFENSIVE; if (_StaminaCost != 0) { SCharacteristicsAndScores &stamina = actingEntity->getScores()._PhysicalScores[SCORES::stamina]; if ( stamina.Current != 0 ) { // nlinfo("Stamina current = %d, cost %d", stamina.Current.getValue(), _StaminaCost); stamina.Current = stamina.Current - _StaminaCost; if (stamina.Current < 0) stamina.Current = 0; } } if ( _HPCost != 0) { actingEntity->changeCurrentHp( (_HPCost) * (-1) ); } CCombatWeapon weapon; CCombatWeapon ammo; if ( _Attacker->getItem( CCombatAttacker::RightHandItem, weapon) ) { if (weapon.Family == ITEMFAMILY::MELEE_WEAPON ) { _MeleeCombat = true; } else if (weapon.Family == ITEMFAMILY::RANGE_WEAPON ) { if ( _Attacker->getItem( CCombatAttacker::Ammo, ammo) ) { // check ammo qty if ( !_Attacker->checkAmmoAmount() ) { PHRASE_UTILITIES::sendSimpleMessage( _Attacker->getEntityRowId(), "EGS_NOT_ENOUGH_AMMO"); _BeingProcessed = false; return; } // lock ammos _Attacker->lockAmmos(); } else { PHRASE_UTILITIES::sendSimpleMessage( _Attacker->getEntityRowId(), "BS_NO_AMMO"); _BeingProcessed = false; return; } } else { // ERROR -> Not a weapon PHRASE_UTILITIES::sendSimpleMessage(_Attacker->getEntityRowId(), "BS_ITEM_INCOMPATIBLE"); _BeingProcessed = false; return; } weapon.SkillValue = _Attacker->getSkillValue(weapon.Skill); } else { weapon.Damage = HandToHandDamage; weapon.DmgType = DMGTYPE::BLUNT; weapon.SpeedInTicks = HandToHandSpeed; // TODO skill BareHandCombat missing // weapon.Quality = (uint16)PHRASE_UTILITIES::getEntityLevel( _Attacker->getEntityRowId(), SKILLS::BareHandCombat); weapon.Quality = (uint16)PHRASE_UTILITIES::getEntityLevel( _Attacker->getEntityRowId(), SKILLS::SFM1H); // TODO //weapon.SkillValue = 10;//_Attacker->getEntity()->getSkills()._Skills[ SKILLS::BareHandCombat ].Current;//(uint16)PHRASE_UTILITIES::getEntityLevel( _Attacker->getEntityRowId(), SKILLS::BareHandCombat); weapon.SkillValue = _Attacker->getEntity()->getSkills()._Skills[ SKILLS::SFM1H ].Current; weapon.Skill = SKILLS::SFM1H;//SKILLS::BareHandCombat; } // compute latency end date const NLMISC::TGameCycle time = CTickEventHandler::getGameCycle(); _LatencyEndDate = time + _HitRateModifier + weapon.SpeedInTicks + ammo.SpeedInTicks ; // test phrase success sint32 damage = 0; if (!testPhraseSuccess()) { // Total failure // send miss message _ExecutionBehaviour.Combat.DamageType = (ammo.Damage != 0) ? ammo.DmgType : weapon.DmgType; _ExecutionBehaviour.Combat.AttackIntensity = PHRASE_UTILITIES::getAttackIntensity(_SabrinaCost / 10); _ExecutionBehaviour.Combat.ImpactIntensity = 0; _ExecutionBehaviour.Combat.KillingBlow = 0; if (_PhraseSuccessDamageFactor == 0.0f) { // failed combat->pushEvent( COMBAT_HISTORY::Miss ); PHRASE_UTILITIES::sendCombatResistMessages( _Attacker->getEntityRowId(), _Defender->getEntityRowId() ); } else if (_PhraseSuccessDamageFactor < 0.0f) { // fumble combat->pushEvent( COMBAT_HISTORY::Fumble ); PHRASE_UTILITIES::sendFumbleMessage( _Attacker->getEntityRowId(), _Defender->getEntityRowId() ); // double latency ! _LatencyEndDate = (_LatencyEndDate-time) * 2 + time; } } else { // get shield CCombatShield shield; _Defender->getShield(shield); // determine localisation PHRASE_UTILITIES::TPairSlotShield localisation; sint8 adjustment = PHRASE_UTILITIES::getLocalisationSizeAdjustement( _Attacker->getEntityRowId(), _Defender->getEntityRowId() ); if ( _ForcedLocalisation != SLOT_EQUIPMENT::UNDEFINED ) localisation = PHRASE_UTILITIES::getLocalisation( shield.ShieldType, adjustment, _ForcedLocalisation ); else localisation = PHRASE_UTILITIES::getLocalisation( shield.ShieldType, adjustment ); // test against opponent defense if ( testOpponentDefense( _Defender->getEntityRowId(), localisation ) ) { // opponent dodged the attack, send messages DEBUGLOG(" Actor %u, target %u has dodged", _Attacker->getEntityRowId().getIndex(), _Defender->getEntityRowId().getIndex() ); // update the actor behaviour _ExecutionBehaviour.Combat.DamageType = (ammo.Damage != 0) ? ammo.DmgType : weapon.DmgType; _ExecutionBehaviour.Combat.AttackIntensity = PHRASE_UTILITIES::getAttackIntensity(_SabrinaCost / 10); _ExecutionBehaviour.Combat.ImpactIntensity = 0; _ExecutionBehaviour.Combat.KillingBlow = 0; if (defender->dodgeAsDefense()) combat->pushEvent( COMBAT_HISTORY::Dodge ); else combat->pushEvent( COMBAT_HISTORY::Parry ); } else { if (_PhraseSuccessDamageFactor > 1.0f) { // critical strike PHRASE_UTILITIES::sendCriticalHitMessage( _Attacker->getEntityRowId(), _Defender->getEntityRowId() ); } // Compute damage sint32 attackerLevel; uint16 itemQuality = _MeleeCombat ? weapon.Quality : ammo.Quality; if ( (weapon.SkillValue/10) >= itemQuality ) { // attacker level is the same or higher, compute the mean between attacker and weapon level attackerLevel = (weapon.SkillValue + itemQuality*10)/2; } else { // attacker level is lower, give him a +10 bonus to skill attackerLevel = 10 + weapon.SkillValue; } sint32 weaponDamage = _MeleeCombat ? weapon.Damage : ammo.Damage; damage = sint32( ( weaponDamage * ( 10.0f + float(attackerLevel)) / 260.0f + _DamageModifier ) * _PhraseSuccessDamageFactor ); sint32 damageBeforeArmor = damage; // get damage amplifier on target const CSEffect * effect = defender->lookForSEffect( EFFECT_FAMILIES::MeleeDmgAmpli ); if ( effect ) damage *= effect->getParamValue() / 100; // armor CCombatArmor armor; _Defender->getArmor( localisation.first, armor); // compute armor and shield protections sint32 defenderArmorLevel = ((armor.SkillValue/10) >= armor.Quality) ? ((weapon.SkillValue + armor.Quality)*10)/2 : 10+armor.SkillValue; sint32 defenderShieldLevel = ((shield.SkillValue/10) >= shield.Quality) ? ((shield.SkillValue + shield.Quality)*10)/2 : 10+shield.SkillValue; DMGTYPE::EDamageType dmgType = (ammo.Damage != 0) ? ammo.DmgType : weapon.DmgType; uint32 dmgPreventedByShield = 0; uint32 dmgPreventedByArmor = 0; // get modifier according to armor type float shieldAbsorptionFactor = 1.0; sint32 shieldAbsorptionModifier = 0; float armorAbsorptionFactor = 1.0; sint32 armorAbsorptionModifier = 0; switch(armor.ArmorType) { case ARMORTYPE::LIGHT: armorAbsorptionFactor = _LightArmorAbsorptionMultiplier; armorAbsorptionModifier = _LightArmorAbsorptionModifier; break; case ARMORTYPE::MEDIUM: armorAbsorptionFactor = _MediumArmorAbsorptionMultiplier; armorAbsorptionModifier = _MediumArmorAbsorptionModifier; break; case ARMORTYPE::HEAVY: armorAbsorptionFactor = _HeavyArmorAbsorptionMultiplier; armorAbsorptionModifier = _HeavyArmorAbsorptionModifier; break; default:; }; switch(shield.ArmorType) { case ARMORTYPE::LIGHT: shieldAbsorptionFactor = _LightArmorAbsorptionMultiplier; shieldAbsorptionModifier = _LightArmorAbsorptionModifier; break; case ARMORTYPE::MEDIUM: shieldAbsorptionFactor = _MediumArmorAbsorptionMultiplier; shieldAbsorptionModifier = _MediumArmorAbsorptionModifier; break; case ARMORTYPE::HEAVY: shieldAbsorptionFactor = _HeavyArmorAbsorptionMultiplier; shieldAbsorptionModifier = _HeavyArmorAbsorptionModifier; break; default:; }; switch( dmgType) { case DMGTYPE::BLUNT: if (localisation.second) { dmgPreventedByShield += (uint32)(shield.MaxBluntProtection < (uint32)(0.01 * shield.BluntProtectionFactor * damage) ? shield.MaxBluntProtection : 0.01 * shield.BluntProtectionFactor * damage ); dmgPreventedByShield = (uint32) ((10.0f+ float(defenderShieldLevel))/260.0f * dmgPreventedByShield); dmgPreventedByShield = max( sint32(0), sint32(dmgPreventedByShield * shieldAbsorptionFactor) + shieldAbsorptionModifier ); damage -= dmgPreventedByShield; } dmgPreventedByArmor += (uint32)(armor.MaxBluntProtection < (uint32)(0.01 * armor.BluntProtectionFactor * damage) ? armor.MaxBluntProtection : 0.01 * armor.BluntProtectionFactor * damage ); dmgPreventedByArmor = (uint32) ((10.0f+ float(defenderArmorLevel))/260.0f * dmgPreventedByArmor); dmgPreventedByArmor = max( sint32(0), sint32(dmgPreventedByArmor * armorAbsorptionFactor) + armorAbsorptionModifier ); damage -= dmgPreventedByArmor; break; case DMGTYPE::SLASHING: if (localisation.second) { dmgPreventedByShield += (uint32)(shield.MaxSlashingProtection < (uint32)(0.01 * shield.SlashingProtectionFactor * damage) ? shield.MaxSlashingProtection : 0.01 * shield.SlashingProtectionFactor * damage ); dmgPreventedByShield = (uint32) ((10.0f+ float(defenderShieldLevel))/260.0f * dmgPreventedByShield); dmgPreventedByShield = max( sint32(0), sint32(dmgPreventedByShield * shieldAbsorptionFactor) + shieldAbsorptionModifier ); damage -= dmgPreventedByShield; } dmgPreventedByArmor += (uint32)(armor.MaxSlashingProtection < (uint32)(0.01 * armor.SlashingProtectionFactor * damage) ? armor.MaxSlashingProtection : 0.01 * armor.SlashingProtectionFactor * damage ); dmgPreventedByArmor = (uint32) ((10.0f+ float(defenderArmorLevel))/260.0f * dmgPreventedByArmor); dmgPreventedByArmor = max( sint32(0), sint32(dmgPreventedByArmor * armorAbsorptionFactor) + armorAbsorptionModifier ); damage -= dmgPreventedByArmor; break; case DMGTYPE::PIERCING: if (localisation.second) { dmgPreventedByShield += (uint32)(shield.MaxPiercingProtection < (uint32)(0.01 * shield.PiercingProtectionFactor * damage) ? shield.MaxPiercingProtection : 0.01 * shield.PiercingProtectionFactor * damage ); dmgPreventedByShield = (uint32) ((10.0f+ float(defenderShieldLevel))/260.0f * dmgPreventedByShield); dmgPreventedByShield = max( sint32(0), sint32(dmgPreventedByShield * shieldAbsorptionFactor) + shieldAbsorptionModifier ); damage -= dmgPreventedByShield; } dmgPreventedByArmor += (uint32)(armor.MaxPiercingProtection < (uint32)(0.01 * armor.PiercingProtectionFactor * damage) ? armor.MaxPiercingProtection : 0.01 * armor.PiercingProtectionFactor * damage ); dmgPreventedByArmor = (uint32) ((10.0f+ float(defenderArmorLevel))/260.0f * dmgPreventedByArmor); dmgPreventedByArmor = max( sint32(0), sint32(dmgPreventedByArmor * armorAbsorptionFactor) + armorAbsorptionModifier ); damage -= dmgPreventedByArmor; break; default: break; }; if ( dmgPreventedByShield != 0) { INFOLOG("Entity %u (attacked by %u) Damage prevented by the shield = %u", _Defender->getEntityRowId().getIndex(), _Attacker->getEntityRowId().getIndex(), dmgPreventedByShield); // remove shield hit points // compute the real _DamageAmount of hp removed for the shield _Defender->damageOnShield( dmgPreventedByShield ); //_ShieldLostHp[targetIndex] += (uint32) (dmgPreventedByShield * ( 1 + shieldDeltaLevel * 0.1)); //sentence->LogReportStructure.ShieldAbsorption += dmgPreventedByShield; } if ( dmgPreventedByArmor != 0) { _Defender->damageOnArmor(localisation.first, dmgPreventedByArmor ); INFOLOG("Entity %u (attacked by %u) Damage prevented by the armor = %u",_Defender->getEntityRowId().getIndex(), _Attacker->getEntityRowId().getIndex(), dmgPreventedByArmor); } _ExecutionBehaviour.Combat.DamageType = dmgType; _ExecutionBehaviour.Combat.AttackIntensity = PHRASE_UTILITIES::getAttackIntensity(_SabrinaCost / 10); _ExecutionBehaviour.Combat.ImpactIntensity = PHRASE_UTILITIES::getImpactIntensity( damage, _Defender->getEntityRowId() ); if (damage == 0) { // MAY BE AN BUG -> LOG MANY INFOS FOR DEBUG nlwarning("COMBAT : Entity %s hits entity %s but does 0 damage.", actingEntity->getId().toString().c_str(), defender->getId().toString().c_str() ); nlwarning("Attacker weapon infos : %s", weapon.toString()); nlwarning("attackerLevel = %d, weaponDamage : %d", attackerLevel, weaponDamage); nlwarning(""); nlwarning("Defender armor infos : %s", armor.toString()); nlwarning("Damage before effects and armor : %d", damageBeforeArmor); nlwarning("Damage prevented by armor : %d", dmgPreventedByArmor); nlwarning("Damage prevented by shield : %d", dmgPreventedByShield); } // apply damage if (damage > 0) { applyCombatActions(); _ExecutionBehaviour.DeltaHP = (sint16)((-1)*damage); if ( defender->changeCurrentHp( (-1)*damage ) == true) { // entity has been killed, change the bahaviour of the attacker to set the flag _ExecutionBehaviour.Combat.KillingBlow = 1; // send mission event if ( actingEntity->getId().getType()== RYZOMID::player ) { CMissionEventKill event ( _Defender->getEntityRowId() ); ((CCharacter*) actingEntity)->processMissionEvent( event ); } } else { CPhraseManager::getInstance()->breakCast(attackerLevel,actingEntity,defender); } // add modifier to sentence AI event reports // TODO // test spell break //PHRASE_UTILITIES::testSpellBreakOnDamage( _Defender->getEntityRowId(), _Attacker->getEntityRowId(), damage, damageType); // TODO sint32 lostStamina = (sint32)( damage * _StaminaLossFactor + _StaminaLossModifier); sint32 lostSap = (sint32)(damage * _SapLossFactor + _SapLossModifier); if ( lostStamina != 0 ) { INFOLOG("entity %s lose %d stamina", TheDataset.getEntityId(_Defender->getEntityRowId()).toString().c_str(),lostStamina ); defender->getPhysScores()._PhysicalScores[SCORES::stamina].Current = defender->getPhysScores()._PhysicalScores[SCORES::stamina].Current - lostStamina; //TEMPFIX // clip score to 0 if ( defender->getPhysScores()._PhysicalScores[SCORES::stamina].Current < 0 ) defender->getPhysScores()._PhysicalScores[SCORES::stamina].Current = 0; // add modifier to sentence AI event reports _AiEventReport.addDelta(AI_EVENT_REPORT::Stamina, (-1)*lostStamina); } if ( lostSap != 0 ) { //value = toString( _BaseSapAbsorption[i] ); INFOLOG("entity %s lose %d sap", TheDataset.getEntityId(_Defender->getEntityRowId()).toString().c_str(), lostSap ); defender->getPhysScores()._PhysicalScores[SCORES::sap].Current = defender->getPhysScores()._PhysicalScores[SCORES::sap].Current - lostSap; //TEMPFIX // clip score to 0 if ( defender->getPhysScores()._PhysicalScores[SCORES::sap].Current < 0 ) defender->getPhysScores()._PhysicalScores[SCORES::sap].Current = 0; // add modifier to sentence AI event reports _AiEventReport.addDelta(AI_EVENT_REPORT::Sap, (-1)*lostSap); } // send chat messages PHRASE_UTILITIES::sendHitMessages(_Attacker->getEntityRowId(),_Defender->getEntityRowId(), damage, lostStamina, lostSap); if ( _ExecutionBehaviour.Combat.KillingBlow == 1 ) PHRASE_UTILITIES::sendDeathMessages( _Attacker->getEntityRowId(), _Defender->getEntityRowId() ); // send report for xp gain if actor is a player if (_Attacker->getEntity()->getId().getType() == RYZOMID::player ) ((CCharacter*)(_Attacker->getEntity()))->actionReport( _Defender->getEntity(), _DeltaLevel, ACTNATURE::OFFENSIVE, SKILLS::toString(_AttackSkill) ); } else PHRASE_UTILITIES::sendHitNullMessages( _Attacker->getEntityRowId(), _Defender->getEntityRowId() ); INFOLOG("Entity %s hits entity %s does %u damage", TheDataset.getEntityId(_Attacker->getEntityRowId()).toString().c_str(), TheDataset.getEntityId(_Defender->getEntityRowId()).toString().c_str(), damage ); // --- TODO : INSERT THE REAL HIT LOCATION + CRITICAL HIT MANAGEMENT if ( !_ExecutionBehaviour.Combat.KillingBlow ) combat->pushEvent( COMBAT_HISTORY::HitChest ); } } // update the actor behaviour if ( _ExecutionBehaviour.Behaviour != MBEHAV::UNKNOWN_BEHAVIOUR ) PHRASE_UTILITIES::sendUpdateBehaviour( _Attacker->getEntityRowId(), _ExecutionBehaviour ); // compute aggro sint32 aggro = 0; const sint32 maxPv = defender->getPhysScores()._PhysicalScores[SCORES::hit_points].Max; if (maxPv) { aggro = (-1) * sint32((100.0 * double(damage + _AggroModifier))/double(maxPv) * _AggroMultiplier) ; } // update the repor _AiEventReport.AggroMul = 1.0f; _AiEventReport.AggroAdd = aggro; _AiEventReport.addDelta(AI_EVENT_REPORT::HitPoints, (-1)*damage); PhraseManager->addAiEventReport(_AiEventReport); CCharacter* player = dynamic_cast (_Attacker->getEntity()); if (player) player->setCurrentAction(CLIENT_ACTION_TYPE::Combat,_LatencyEndDate); actingEntity->setActionFlag( RYZOMACTIONFLAGS::Attacks, false ); _BeingProcessed = false; } // apply // //-------------------------------------------------------------- // end() //-------------------------------------------------------------- void CCombatPhrase::end() { if (!_Attacker) return; _BeingProcessed = true; _Attacker->unlockRightItem(); // set the attacks flag of the acting entity CEntityBase *entityPtr = PHRASE_UTILITIES::entityPtrFromId(_Attacker->getEntityRowId()); if (entityPtr) entityPtr->setActionFlag( RYZOMACTIONFLAGS::Attacks, false ); _State = CSPhrase::LatencyEnded; if (_DisengageOnEnd) { PhraseManager->disengage( _Attacker->getEntityRowId(), false, true); } _BeingProcessed = false; CCharacter* player = dynamic_cast (entityPtr); if (player) player->clearCurrentAction(); } // end // //-------------------------------------------------------------- // testOpponentDefense() //-------------------------------------------------------------- bool CCombatPhrase::testOpponentDefense(const TDataSetRow &targetRowId, const PHRASE_UTILITIES::TPairSlotShield &localisation) { sint32 attackerLevel = 0; CCombatWeapon weapon; if (_Attacker->getItem( CCombatAttacker::RightHandItem, weapon) ) { _AttackSkill = weapon.Skill; attackerLevel = _AttackSkillModifier + ((weapon.SkillValue/10) >= weapon.Quality) ? ((weapon.SkillValue + weapon.Quality)*10)/2 : 10 + weapon.SkillValue; attackerLevel /= 10; } else { if ( _RootSkill == SKILLS::unknown ) //TODO barehandedcombat _AttackSkill = SKILLS::SFM1H;//SKILLS::BareHandCombat; else _AttackSkill = _RootSkill; attackerLevel = PHRASE_UTILITIES::getEntityLevel(_Attacker->getEntityRowId(), _AttackSkill,_AttackSkillModifier); } // defender has a malus of one level per attacker beyond the first one const set &aggressors = PhraseManager->getMeleeAggressors(targetRowId); sint32 malus = 0; sint32 nb = aggressors.size(); if (nb) malus = (1-nb)*10; sint32 valueTemp = _Defender->getDefenseValue(); sint32 defenderLevel = max( sint32((valueTemp + malus ) / 10), (sint32)0); _DeltaLevel = attackerLevel - defenderLevel; // oposition test uint8 chances = PHRASE_UTILITIES::getSuccessChance( defenderLevel - attackerLevel ); // TEMP HACK : limit dodge chances for the moment, wait new rules (more 'fun') chances /= 3; float result = PHRASE_UTILITIES::getSucessFactor(chances, (uint8)RandomGenerator.rand(99) ); if (result>=1.0f) { if ( !_Defender->getEntity()->dodgeAsDefense() ) { PHRASE_UTILITIES::sendMessage( _Attacker->getEntityRowId(), string("EGS_ACTOR_COMBAT_PARRY_E"), targetRowId); PHRASE_UTILITIES::sendMessage( targetRowId, string("EGS_TARGET_COMBAT_PARRY_E"), _Attacker->getEntityRowId()); } else { PHRASE_UTILITIES::sendMessage( _Attacker->getEntityRowId(), string("EGS_ACTOR_COMBAT_DODGE_E"), targetRowId); PHRASE_UTILITIES::sendMessage( targetRowId, string("EGS_TARGET_COMBAT_DODGE_E"), _Attacker->getEntityRowId()); } return true; } return false; } // testOpponentDefense // //-------------------------------------------------------------- // testPhraseSuccess() //-------------------------------------------------------------- bool CCombatPhrase::testPhraseSuccess() { sint32 skillValue; if ( _RootSkill != SKILLS::unknown ) skillValue = _Attacker->getSkillValue( _RootSkill ); else { //TODO skill barehanded combat CCombatWeapon weapon; if (_Attacker->getItem( CCombatAttacker::RightHandItem, weapon) ) { skillValue = weapon.SkillValue; } else { //skillValue = _Attacker->getSkillValue( SKILLS::BareHandCombat ); skillValue = _Attacker->getSkillValue( SKILLS::SFM1H ); } } const sint16 relativeLevel = (skillValue + _SabrinaCredit - 2*_SabrinaCost) / 10; // oposition test uint8 chances = PHRASE_UTILITIES::getSuccessChance( relativeLevel ); _PhraseSuccessDamageFactor = PHRASE_UTILITIES::getSucessFactor(chances, (uint8)RandomGenerator.rand(99) ); // nlinfo("_PhraseSuccessDamageFactor = %f, (skill+bonus = %d, difficulty = %d)", _PhraseSuccessDamageFactor, (skillValue + _SabrinaCredit - _SabrinaCost), _SabrinaCost); if (_PhraseSuccessDamageFactor == 0.0f) { // failed return false; } else if (_PhraseSuccessDamageFactor < 0.0f) { // fumble return false; } return true; } // testPhraseSuccess // //-------------------------------------------------------------- // checkTargetValidity() //-------------------------------------------------------------- bool CCombatPhrase::checkTargetValidity( const TDataSetRow &targetRowId, string &errorCode ) { if ( !targetRowId.isValid() || !TheDataset.isDataSetRowStillValid(targetRowId) ) { errorCode = "BS_INVALID_TARGET"; return false; } if (targetRowId == _Attacker->getEntityRowId() && TheDataset.getEntityId(_Attacker->getEntityRowId()).getType() == RYZOMID::player) { errorCode = "BS_INVALID_TARGET"; return false; } if ( ! PHRASE_UTILITIES::testOffensiveActionAllowed(_Attacker->getEntityRowId(), targetRowId, errorCode) ) { return false; } return true; } // checkTargetValidity // //-------------------------------------------------------------- // checkPhraseCost() //-------------------------------------------------------------- bool CCombatPhrase::checkPhraseCost( string &errorCode ) { if (!_Attacker || !_Attacker->getEntity()) return false; // only check costs for players if ( TheDataset.getEntityId(_Attacker->getEntityRowId()).getType() != RYZOMID::player) return true; const SCharacteristicsAndScores &stamina = _Attacker->getEntity()->getScores()._PhysicalScores[SCORES::stamina]; if ( stamina.Current < _StaminaCost) { errorCode = "EGS_TOO_EXPENSIVE_STAMINA"; return false; } const SCharacteristicsAndScores &hp = _Attacker->getEntity()->getScores()._PhysicalScores[SCORES::hit_points]; if ( hp.Current < _HPCost) { errorCode = "EGS_TOO_EXPENSIVE_HP"; return false; } return true; } // checkPhraseCost // //-------------------------------------------------------------- // createDefender() //-------------------------------------------------------------- void CCombatPhrase::createDefender( const TDataSetRow &targetRowId ) { if (_Defender != NULL) { delete _Defender; _Defender = NULL; } if ( !targetRowId.isValid() || !TheDataset.isDataSetRowStillValid(targetRowId) ) return; // create defender structure if ( TheDataset.getEntityId(targetRowId).getType() == RYZOMID::player ) { _Defender = new CCombatDefenderPlayer(targetRowId); } else { _Defender = new CCombatDefenderAI(targetRowId); } } // createDefender // //-------------------------------------------------------------- // validateCombatActions() //-------------------------------------------------------------- bool CCombatPhrase::validateCombatActions( string &errorCode ) { const uint size = _CombatActions.size(); for (uint i = 0 ; i < size ; ++i) { if ( _CombatActions[i] != NULL) { if (!_CombatActions[i]->validate(this, errorCode)) return false; } } return true; } // validateCombatActions // //-------------------------------------------------------------- // applyCombatActions() //-------------------------------------------------------------- void CCombatPhrase::applyCombatActions() { if (!_Defender) return; const uint size = _CombatActions.size(); for (uint i = 0 ; i < size ; ++i) { if ( _CombatActions[i] != NULL) { _CombatActions[i]->setTarget( _Defender->getEntityRowId() ); _CombatActions[i]->apply(this); } } } // validateCombatActions //