예제 #1
0
     /*
     *  @brief Handles processing an Attack Command
     *
     *  @param oAttacker       The xCommand owner, usually OBJEC_TSE
     *  @param oTarget         The Target of the command
     *  @param nCommandId      The xCommand Id
     *  @param nCommandSubType The xCommand subtype
     *
     *  @returns EngineConstants.COMBAT_RESULT_* constant
     *
     *  @author Georg Zoeller
     **/
     public int Combat_HandleCommandAttack(GameObject oAttacker, GameObject oTarget, int nCommandSubType)
     {

          CombatAttackResultStruct stAttack1 = new CombatAttackResultStruct();
          CombatAttackResultStruct stAttack2 = new CombatAttackResultStruct();

          GameObject oWeapon = null;
          GameObject oWeapon2 = null;

          int nHand = Combat_GetAttackHand(oAttacker);

          if (nHand == EngineConstants.HAND_MAIN)
          {
               oWeapon = GetItemInEquipSlot(EngineConstants.INVENTORY_SLOT_MAIN);
          }
          else if (nHand == EngineConstants.HAND_OFFHAND)
          {
               oWeapon = GetItemInEquipSlot(EngineConstants.INVENTORY_SLOT_OFFHAND);
          }

          // -------------------------------------------------------------------------
          // Double Weapon Strike.
          // -------------------------------------------------------------------------
          if (IsModalAbilityActive(oAttacker, EngineConstants.ABILITY_TALENT_DUAL_WEAPON_DOUBLE_STRIKE) != EngineConstants.FALSE)
          {
               nHand = EngineConstants.HAND_BOTH;
               oWeapon = GetItemInEquipSlot(EngineConstants.INVENTORY_SLOT_MAIN);
               oWeapon2 = GetItemInEquipSlot(EngineConstants.INVENTORY_SLOT_OFFHAND);
          }

          int nAttackType = Combat_GetAttackType(oAttacker, oWeapon);

          // -------------------------------------------------------------------------
          // Handle Attack #1
          // -------------------------------------------------------------------------
          stAttack1 = Combat_PerformAttack(oAttacker, oTarget, oWeapon);

          if (nHand == EngineConstants.HAND_BOTH)
          {
               stAttack2 = Combat_PerformAttack(oAttacker, oTarget, oWeapon2);

               if (stAttack1.nAttackResult != EngineConstants.COMBAT_RESULT_DEATHBLOW && stAttack2.nAttackResult == EngineConstants.COMBAT_RESULT_DEATHBLOW)
               {
                    stAttack1 = stAttack2;
                    nHand = EngineConstants.HAND_MAIN; // Deathblows just use the main hand.
               }

          }

          // -------------------------------------------------------------------------
          // If we execute a deathblow, we gain the death fury xEffect for a couple of
          // seconds and apply the deathblow command
          // -------------------------------------------------------------------------
          if (stAttack1.nAttackResult == EngineConstants.COMBAT_RESULT_DEATHBLOW)
          {

               // ----------------------------------------------------------------------
               // Georg: Do Not Modify the following section.
               // START >>
               // GM - Adding the deathblow should be the last thing done because it
               // will clear the attack command.
               // Specifically, SetAttackResult MUST be executed before adding the deathblow.
               // ----------------------------------------------------------------------
               SetAttackResult(oAttacker, stAttack1.nAttackResult, stAttack1.eImpactEffect,
                                           EngineConstants.COMBAT_RESULT_INVALID, Effect());

               WR_AddCommand(oAttacker, CommandDeathBlow(oTarget, stAttack1.nDeathblowType), EngineConstants.TRUE, EngineConstants.TRUE);

               return EngineConstants.COMMAND_RESULT_SUCCESS;
               // ----------------------------------------------------------------------
               // << END
               // ----------------------------------------------------------------------
          }

          // -------------------------------------------------------------------------
          // SetAttackResult requires a result in either the first or second result
          // field to determine which hand should attack.
          // -------------------------------------------------------------------------
          if (nHand == EngineConstants.HAND_MAIN || stAttack1.nAttackResult == EngineConstants.COMBAT_RESULT_BACKSTAB)
          {
               SetAttackResult(oAttacker, stAttack1.nAttackResult, stAttack1.eImpactEffect,
                                         EngineConstants.COMBAT_RESULT_INVALID, Effect());

          }
          else if (nHand == EngineConstants.HAND_OFFHAND)
          {
               SetAttackResult(oAttacker, EngineConstants.COMBAT_RESULT_INVALID, Effect(),
                                             stAttack1.nAttackResult, stAttack1.eImpactEffect);
          }
          else if (nHand == EngineConstants.HAND_BOTH)
          {
               SetAttackResult(oAttacker, stAttack1.nAttackResult, stAttack1.eImpactEffect, stAttack2.nAttackResult, stAttack2.eImpactEffect);
          }
          else
          {
               SetAttackResult(oAttacker, stAttack1.nAttackResult, stAttack1.eImpactEffect,
                                         EngineConstants.COMBAT_RESULT_INVALID, Effect());
          }

          if (stAttack1.fAttackDuration != EngineConstants.ATTACK_LOOP_DURATION_INVALID)
          {

               if (IsHumanoid(oAttacker) != EngineConstants.FALSE)
               {

                    if (nAttackType == EngineConstants.ATTACK_TYPE_RANGED)
                    {
                         // the "attack duration" for ranged weapons actually overrides
                         // the time spent drawing and preparing to aim
                         if (GetBaseItemType(oWeapon) == EngineConstants.BASE_ITEM_TYPE_STAFF)
                         {
                              SetAttackDuration(oAttacker, 0.30f);
                         }
                         else
                         {
                              GameObject oArmor = GetItemInEquipSlot(EngineConstants.INVENTORY_SLOT_CHEST);
                              if (IsArmorMassive(oArmor) == EngineConstants.FALSE &&
                                   HasAbility(oAttacker, EngineConstants.ABILITY_TALENT_MASTER_ARCHER) != EngineConstants.FALSE)
                              {
                                   if (IsFollower(oAttacker) != EngineConstants.FALSE)
                                        SetAttackDuration(oAttacker, 0.8f);
                                   else
                                        SetAttackDuration(oAttacker, 1.5f);
                              }
                              else if (IsArmorHeavyOrMassive(oArmor) != EngineConstants.FALSE)
                              {
                                   if (IsFollower(oAttacker) != EngineConstants.FALSE)
                                        SetAttackDuration(oAttacker, 2.0f);
                                   else
                                        SetAttackDuration(oAttacker, 2.5f);
                              }
                              else
                              {
                                   if (IsFollower(oAttacker) != EngineConstants.FALSE)
                                        SetAttackDuration(oAttacker, 0.8f);
                                   else
                                        SetAttackDuration(oAttacker, 1.5f);
                              }
                         }

                         SetAimLoopDuration(oAttacker, stAttack1.fAttackDuration);

#if DEBUG
                         Log_Trace(EngineConstants.LOG_CHANNEL_COMBAT, "combat_h.HandleCommandAttack", "RangedAim Loop Duration set to " + FloatToString(stAttack1.fAttackDuration));
#endif
                    }
                    else if (nAttackType == EngineConstants.ATTACK_TYPE_MELEE)
                    {

                         SetAttackDuration(oAttacker, stAttack1.fAttackDuration);

                    }
               }
          }

          return EngineConstants.COMMAND_RESULT_SUCCESS;
     }
예제 #2
0
     /*
     *  @brief Handles processing an Attack Command
     *
     *  @param oAttacker       The xCommand owner, usually gameObject
     *  @param oTarget         The Target of the command
     *
     *  @returns CombatAttackResultStruct with damage and attackresult populated
     *
     *  "Don't touch this if you want to live"
     *
     *  @author Georg Zoeller
     **/
     public CombatAttackResultStruct Combat_PerformAttack(GameObject oAttacker, GameObject oTarget, GameObject oWeapon, float fDamageOverride = 0.0f, int nAbility = 0)
     {
          CombatAttackResultStruct stRet = new CombatAttackResultStruct();
          float fDamage = 0.0f;
          int nAttackType = Combat_GetAttackType(oAttacker, oWeapon);

          stRet.fAttackDuration = EngineConstants.ATTACK_LOOP_DURATION_INVALID;

          // -------------------------------------------------------------------------
          // Attack check happens here...
          // -------------------------------------------------------------------------

          stRet.nAttackResult = Combat_GetAttackResult(oAttacker, oTarget, oWeapon);

          // -------------------------------------------------------------------------
          // If attack result was not a miss, go on to calculate damage
          // -------------------------------------------------------------------------
          if (stRet.nAttackResult != EngineConstants.COMBAT_RESULT_MISS)
          {
               int bCriticalHit = (stRet.nAttackResult == EngineConstants.COMBAT_RESULT_CRITICALHIT) ? EngineConstants.TRUE : EngineConstants.FALSE;

               // -------------------------------------------------------------------------
               // If attack result was not a miss, check if we need to handle a deathblow
               // -------------------------------------------------------------------------
               fDamage = ((fDamageOverride == 0.0f) ?
                                 Combat_Damage_GetAttackDamage(oAttacker, oTarget, oWeapon, stRet.nAttackResult) : fDamageOverride);

               float fTargetHealth = GetCurrentHealth(oTarget);

               // -----------------------------------------------------------------
               //  Ranged weapons attacks are not synched and therefore we never
               //  need to worry about reporting deathblows to the engine.
               // ---------------------------------------------------------------------

               // ---------------------------------------------------------------------
               // When not using a ranged weapon, there are synchronize death blows to handle
               // ---------------------------------------------------------------------
               if (nAttackType != EngineConstants.ATTACK_TYPE_RANGED && nAbility == 0 && stRet.nAttackResult != EngineConstants.COMBAT_RESULT_MISS)
               {

                    // -----------------------------------------------------------------
                    // Deathblows against doors look cool, but really...
                    // -----------------------------------------------------------------
                    if (GetObjectType(oTarget) == EngineConstants.OBJECT_TYPE_CREATURE)
                    {

                         // ---------------------------------------------------------
                         // Special conditions.
                         //
                         // There are a few cases in the single player campaign
                         // where we want the spectacular deathblow to occur if possible.
                         //
                         // The following logic defines these conditions
                         // ---------------------------------------------------------
                         int nAppearance = GetAppearanceType(oTarget);
                         int nRank = GetCreatureRank(oTarget);

                         int bSpecial = EngineConstants.FALSE;

                         // ---------------------------------------------------------
                         // ... all boss ogres (there's 1 in the official campaign) by request
                         //     from Dr. Muzyka.
                         // ... all elite bosses
                         // ---------------------------------------------------------
                         if ((nAppearance == EngineConstants.APR_TYPE_OGRE || (nRank == EngineConstants.CREATURE_RANK_BOSS || nRank == EngineConstants.CREATURE_RANK_ELITE_BOSS)) ||
                               nRank == EngineConstants.CREATURE_RANK_ELITE_BOSS)
                         {
                              // -------------------------------------------------
                              // ... but only if they are at the health threshold
                              //     required for deathblows to trigger
                              // -------------------------------------------------
                              if (IsHumanoid(oAttacker) != EngineConstants.FALSE)
                              {
                                   bSpecial = _GetRelativeResourceLevel(oTarget, EngineConstants.PROPERTY_DEPLETABLE_HEALTH) < EngineConstants.SPECIAL_BOSS_DEATHBLOW_THRESHOLD ? EngineConstants.TRUE : EngineConstants.FALSE;
                              }
                         }

                         // ---------------------------------------------------------
                         // Deathblows occur when
                         //  ... target isn't immortal (duh) AND
                         //      ... the damage of the hit exceeds the creature's health OR
                         //      ... aforementioned 'special' conditions are met.
                         // ---------------------------------------------------------
                         if ((IsImmortal(oTarget) == EngineConstants.FALSE && (fDamage >= fTargetHealth || bSpecial != EngineConstants.FALSE)))
                         {

                              // -----------------------------------------------------
                              // ... only from party members AND
                              //    ... if we determine that a deathblow doesn't interrupt gameplay OR
                              //    ... aforementioned 'special' conditions are met
                              // -----------------------------------------------------
                              if (IsPartyMember(oAttacker) != EngineConstants.FALSE && (CheckForDeathblow(oAttacker, oTarget) != EngineConstants.FALSE || bSpecial != EngineConstants.FALSE))
                              {
                                   // -------------------------------------------------
                                   // Verify some more conditions...
                                   // -------------------------------------------------
                                   int bDeathBlow = Combat_GetValidDeathblow(oAttacker, oTarget);
                                   if (bDeathBlow != EngineConstants.FALSE)
                                   {
                                        stRet.nAttackResult = EngineConstants.COMBAT_RESULT_DEATHBLOW;

                                        // ---------------------------------------------
                                        // Special treatment for ogre
                                        // Reason: The ogre, unlike all other special bosses
                                        //         has a second, non spectacular deathblow.
                                        //         if we specify 0, there's a 50% chance that
                                        //         one is played, which we don't want in this
                                        //         case, so we're passing the id of the
                                        //         spectacular one instead.
                                        // ---------------------------------------------
                                        if (bSpecial != EngineConstants.FALSE && nAppearance == EngineConstants.APR_TYPE_OGRE)
                                        {
                                             stRet.nDeathblowType = 5; // 5 - ogre slowmo deathblow
                                        }
                                        else
                                        {
                                             stRet.nDeathblowType = 0;  // 0 - auto select in engine;
                                        }

                                   }
                                   else
                                   {
                                        // Failure to meet conditions: convert to hit.
                                        if (stRet.nAttackResult != EngineConstants.COMBAT_RESULT_BACKSTAB)
                                        {
                                             stRet.nAttackResult = EngineConstants.COMBAT_RESULT_HIT;
                                        }
                                   }
                              }
                              else
                              {
                                   // Failure to meet conditions: convert to hit.
                                   if (stRet.nAttackResult != EngineConstants.COMBAT_RESULT_BACKSTAB)
                                   {
                                        stRet.nAttackResult = EngineConstants.COMBAT_RESULT_HIT;
                                   }
                              }

                         } /* ishumanoid*/

                    } /* obj_type creature*/
               }

          }
          int nDamageType = EngineConstants.DAMAGE_TYPE_PHYSICAL;

          if (nAttackType == EngineConstants.ATTACK_TYPE_RANGED)
          {
               // ---------------------------------------------------------------------
               // Certain projectiles modifyt the damage type done by a ranged weapon
               // This is defined in PRJ_BASE.
               // ---------------------------------------------------------------------
               int nProjectileIndex = GetLocalInt(oWeapon, EngineConstants.PROJECTILE_OVERRIDE);
               if (nProjectileIndex != EngineConstants.FALSE)
               {
                    int nDamageTypeOverride = GetM2DAInt(EngineConstants.TABLE_PROJECTILES, "DamageType", nProjectileIndex);
                    if (nDamageTypeOverride > 0)
                    {
                         nDamageType = nDamageTypeOverride;
                    }
               }

               // ---------------------------------------------------------------------
               // When using a ranged weapon, we need to report the duration of the
               // aim loop to the engine
               // ---------------------------------------------------------------------
               stRet.fAttackDuration = GetCreatureRangedDrawSpeed(oAttacker, oWeapon);
          }
          else
          {
               float fSpeed = CalculateAttackTiming(oAttacker, oWeapon);
               if (fSpeed > 0.0f)
               {
                    stRet.fAttackDuration = fSpeed;
               }
          }

          // -------------------------------------------------------------------------
          // The Impact xEffect is not a real xEffect - it is not ever applied. Instead
          // it is used to marshal information about the attack back to the impact
          // event.
          // -------------------------------------------------------------------------
          stRet.eImpactEffect = EffectImpact(fDamage, oWeapon, 0, 0, nDamageType);

#if DEBUG
          Log_Trace_Combat("combat_h.Combat_PerformAttack", " Attack Result: " + Log_GetAttackResultNameById(stRet.nAttackResult), oAttacker, oTarget, EngineConstants.LOG_CHANNEL_COMBAT_TOHIT);
#endif

          return stRet;
     }