#include #include "AttackManager.h" #include "WeenieObject.h" #include "World.h" #include "Player.h" #include "WeenieFactory.h" #include "Ammunition.h" #include "CombatFormulas.h" #include "combat/MeleeAttackEventData.h" // TODO: Move these up to AttackEventData? void CMeleeAttackEvent::CalculateAtt(CWeenieObject *weapon, STypeSkill& weaponSkill, uint32_t& weaponSkillLevel) { if (weapon->AsMonster()) { uint32_t bustedMobSkillHack = 0; if (weapon->InqSkill(UNARMED_COMBAT_SKILL, bustedMobSkillHack, false) && bustedMobSkillHack > 0) { weaponSkill = LIGHT_WEAPONS_SKILL; weaponSkillLevel = bustedMobSkillHack; } else { weaponSkill = LIGHT_WEAPONS_SKILL; _weenie->InqSkill(weaponSkill, weaponSkillLevel, FALSE); } } else { float offenseMod = weapon->GetOffenseMod(); weaponSkill = SkillTable::OldToNewSkill((STypeSkill)weapon->InqIntQuality(WEAPON_SKILL_INT, LIGHT_WEAPONS_SKILL, TRUE)); weaponSkillLevel = 0; if (_weenie->InqSkill(weaponSkill, weaponSkillLevel, FALSE)) { weaponSkillLevel = (uint32_t)(weaponSkillLevel * offenseMod); } } } //float CMeleeAttackEvent::CalculateDef() //{ // CWeenieObject *weapon = _weenie->GetWieldedCombat(_combat_style); // if (weapon) // { // float defenseMod = weapon->GetMeleeDefenseMod(); // return defenseMod; // } // // return CAttackEventData::CalculateDef(); //} void CMeleeAttackEvent::Setup() { uint32_t attack_motion = 0; uint32_t weapon_id = 0; uint32_t style = _weenie->get_minterp()->InqStyle(); if (!_do_attack_animation) { if (_weenie->_combatTable) { CWeenieObject *weapon = _weenie->GetWieldedCombat(_combat_style); if (weapon) { //_max_attack_distance = weapon->InqFloatQuality(WEAPON_LENGTH_FLOAT, 0.5); //todo: this would be interesting but something makes the character still move next to the target anyway. Is it the client? weapon_id = weapon->GetID(); AttackType attack_type = (AttackType)weapon->InqIntQuality(ATTACK_TYPE_INT, 0); if (attack_type == (Thrust_AttackType | Slash_AttackType)) { if (_attack_power >= 0.25f) attack_type = Slash_AttackType; else attack_type = Thrust_AttackType; } // Different rules for Dual Wield vs Onehand and shield. if (style == Motion_DualWieldCombat) { // Dual Wield can use both slash and thrust animations based on power. switch (attack_type) { case DoubleThrust_AttackType | DoubleSlash_AttackType: if (_attack_power >= 0.25f) attack_type = DoubleSlash_AttackType; else attack_type = DoubleThrust_AttackType; break; case TripleThrust_AttackType | TripleSlash_AttackType: if (_attack_power >= 0.25f) attack_type = TripleSlash_AttackType; else attack_type = TripleThrust_AttackType; break; } } else if (style == Motion_SwordShieldCombat) { // Force Thrust animation when use a shield with a multi-strike weapon. switch (attack_type) { case DoubleThrust_AttackType | DoubleSlash_AttackType: attack_type = DoubleThrust_AttackType; break; case TripleThrust_AttackType | TripleSlash_AttackType: attack_type = TripleThrust_AttackType; break; } } if (CombatManeuver *combat_maneuver = _weenie->_combatTable->TryGetCombatManuever(style, attack_type, _attack_height)) { attack_motion = combat_maneuver->motion; // UA full speed attacks (Low only) need 3 removed to get the correct animation. if (weapon->m_Qualities.GetInt(DEFAULT_COMBAT_STYLE_INT,0) == Unarmed_CombatStyle && _attack_power <= 0.25f && _attack_height == 3) attack_motion -= 3; } } } if (!attack_motion) { switch (_attack_height) { case LOW_ATTACK_HEIGHT: attack_motion = Motion_AttackLow1; break; case MEDIUM_ATTACK_HEIGHT: attack_motion = Motion_AttackMed1; break; case HIGH_ATTACK_HEIGHT: attack_motion = Motion_AttackHigh1; break; default: { Cancel(); return; } } if (_attack_power >= 0.25f) attack_motion += 3; if (_attack_power >= 0.75f) attack_motion += 3; if (_attack_power < 0.0f || _attack_power > 1.0f) { Cancel(); return; } } // melee attacks can charge! m_bCanCharge = true; _do_attack_animation = attack_motion; } uint32_t quickness = 0; _weenie->m_Qualities.InqAttribute(QUICKNESS_ATTRIBUTE, quickness, FALSE); int weaponAttackTime = _weenie->GetAttackTimeUsingWielded(); int creatureAttackTime = max(0, 120 - (((int)quickness - 60) / 2)); //we reach 0 attack speed at 300 quickness int attackTime = (creatureAttackTime + weaponAttackTime) / 2; //our attack time is the average between our speed and the speed of our weapon. attackTime = max(0, min(120, attackTime)); _attack_speed = 2.25f - (attackTime * (1.0 / 70.0)); _attack_speed = max(min(_attack_speed, 2.25f), 0.8f); //old formula: //int attackTime = max(0, min(120, _weenie->GetAttackTimeUsingWielded())); //_attack_speed = 1.0 / (1.0 / (1.0 + ((120 - attackTime) * (0.005)))); CAttackEventData::Setup(); } void CMeleeAttackEvent::OnReadyToAttack() { if (_do_attack_animation) { MovementParameters params; params.sticky = 1; params.can_charge = 1; params.modify_interpreted_state = 1; params.speed = _attack_speed; params.action_stamp = ++_weenie->m_wAnimSequence; params.autonomous = 0; _weenie->stick_to_object(_target_id); ExecuteAnimation(_do_attack_animation, ¶ms); } else { Finish(); } } void CMeleeAttackEvent::OnAttackAnimSuccess(uint32_t motion) { Finish(); } void CMeleeAttackEvent::Finish() { CWeenieObject *target = GetTarget(); if (!target && _target_id) { Cancel(WERROR_OBJECT_GONE); return; } Done(); } void CMeleeAttackEvent::HandleAttackHook(const AttackCone &cone) { CWeenieObject *target = GetTarget(); if (!target || !IsValidTarget()) { return; } int preVarianceDamage = 0; float variance = 0.0f; DAMAGE_TYPE damageType = DAMAGE_TYPE::UNDEF_DAMAGE_TYPE; bool isBodyPart = false; CWeenieObject *weapon = _weenie->GetWieldedCombat(_combat_style); if (!weapon || weapon->GetAttackDamage() == 0) //if we still don't have a weapon use our body parts { weapon = _weenie; if (_weenie->m_Qualities._body) { BodyPart *part = _weenie->m_Qualities._body->_body_part_table.lookup(cone.part_index); if (part) { isBodyPart = true; damageType = part->_dtype; preVarianceDamage = part->_dval; variance = part->_dvar; } } CWeenieObject *gloverOrBoots; if (_attack_power >= 0.75f) //this is a kick gloverOrBoots = _weenie->GetWielded(FOOT_WEAR_LOC); else //this is a punch gloverOrBoots = _weenie->GetWielded(HAND_WEAR_LOC); if (gloverOrBoots) { damageType = (DAMAGE_TYPE)gloverOrBoots->InqIntQuality(DAMAGE_TYPE_INT, damageType); preVarianceDamage += gloverOrBoots->GetAttackDamage(); variance = gloverOrBoots->InqFloatQuality(DAMAGE_VARIANCE_FLOAT, variance); } } if (!isBodyPart) { preVarianceDamage = weapon->GetAttackDamage(); variance = weapon->InqFloatQuality(DAMAGE_VARIANCE_FLOAT, 0.0f); damageType = weapon->InqDamageType(); } //todo: maybe handle this differently as to integrate all possible damage type combos if (damageType == (DAMAGE_TYPE::SLASH_DAMAGE_TYPE | DAMAGE_TYPE::PIERCE_DAMAGE_TYPE)) { // Damage type should always be Pierce for multi-strike Thrust animations, not slashing. if (_attack_power >= 0.25f && !(_do_attack_animation >= Motion_DoubleThrustLow && _do_attack_animation <= Motion_TripleThrustHigh)) damageType = DAMAGE_TYPE::SLASH_DAMAGE_TYPE; else damageType = DAMAGE_TYPE::PIERCE_DAMAGE_TYPE; } else if (damageType == (DAMAGE_TYPE::SLASH_DAMAGE_TYPE | DAMAGE_TYPE::FIRE_DAMAGE_TYPE)) { //todo: as far as I know only the Mattekar Claw had this, figure out what it did exactly, was it like this? or was it a bit of both damages? //or even a chance for fire damage? if (_attack_power >= 0.25f) damageType = DAMAGE_TYPE::SLASH_DAMAGE_TYPE; else damageType = DAMAGE_TYPE::FIRE_DAMAGE_TYPE; } CWeenieObject *shield = _weenie->GetWieldedCombat(COMBAT_USE::COMBAT_USE_SHIELD); constexpr float weaponBurdenFactor = 1.0f / 450.0f; constexpr float shieldBurdenFactor = 1.0f / 680.0f; if (_weenie->AsMonster() && !_weenie->AsPlayer()) _attack_power = 0.5f; float weaponBurden = 0.0f; float shieldBurden = 0.0f; if (weapon != _weenie) weaponBurden = (float)weapon->InqIntQuality(ENCUMB_VAL_INT, 0) * weaponBurdenFactor; if (shield != NULL) // TODO: Consider adding burden calc to AttackEventData { shieldBurden = (float)shield->InqIntQuality(ENCUMB_VAL_INT, 0); shieldBurden *= (shield->GetPlacementFrameID() == Shield) ? shieldBurdenFactor : weaponBurdenFactor; } float powerStamMod = 0.25 + _attack_power * 0.75; powerStamMod = powerStamMod > 1.0 ? 1.0 : powerStamMod; float equipStamCost = 2.0 + (weaponBurden + shieldBurden); int necessaryStam = max(1, (int)(equipStamCost * powerStamMod + 0.5)); if (_weenie->AsPlayer()) { // this lowers the max endurance needed for getting top bonus to 300 vs 400 uint32_t endurance = 0; float necStamMod = 1.0; _weenie->m_Qualities.InqAttribute(ENDURANCE_ATTRIBUTE, endurance, true); if (endurance >= 50) { necStamMod = ((float)(endurance * endurance) * -0.000003175) - ((float)endurance * 0.0008889) + 1.052; necStamMod = min(max(necStamMod, 0.5f), 1.0f); necessaryStam = (int)(necessaryStam * necStamMod + Random::RollDice(0.0, 1.0)); // little sprinkle of luck } } necessaryStam = max(necessaryStam, 1); bool hadEnoughStamina = true; if (_weenie->GetStamina() < necessaryStam) { hadEnoughStamina = false; _attack_power = 0.00f; _weenie->SetStamina(0, true); // you lose all current stam } else _weenie->AdjustStamina(-necessaryStam); STypeSkill weaponSkill = STypeSkill::UNDEF_SKILL; uint32_t weaponSkillLevel = 0; CalculateAtt(weapon, weaponSkill, weaponSkillLevel); if (!hadEnoughStamina) { if (CPlayerWeenie *pPlayer = _weenie->AsPlayer()) { pPlayer->SendText("You're exhausted!", LTT_ERROR); } } DamageEventData dmgEvent; dmgEvent.source = _weenie; dmgEvent.weapon = weapon; dmgEvent.damage_form = DF_MELEE; dmgEvent.damage_type = damageType; dmgEvent.attackSkill = weaponSkill; dmgEvent.attackSkillLevel = weaponSkillLevel; dmgEvent.preVarianceDamage = preVarianceDamage; dmgEvent.variance = variance; HandlePerformAttack(target, dmgEvent); int cleaveTargets = weapon->InqIntQuality(CLEAVING_INT, 1) - 1; if (cleaveTargets) { std::list lpNearby; g_pWorld->EnumNearby(dmgEvent.source, _max_attack_distance, &lpNearby); int numTargets = cleaveTargets; for (auto tg : lpNearby) { if (!numTargets) break; if (tg == target) continue; if (_weenie->m_Qualities.id != 1 && tg->m_Qualities.id != 1) // Don't cleave mobs if we are a mob. Where 1 is the WCID for a player (always 1). continue; if (!tg->IsAttackable() || (tg->_IsPlayer() && _weenie->_IsPlayer() && ((!_weenie->IsPK() || !tg->IsPK()) && (!_weenie->IsPKLite() || !tg->IsPKLite())))) continue; if (tg->m_Qualities.m_WeenieType == CombatPet_WeenieType) continue; if (tg->HeadingFrom(_weenie, true) < CLEAVING_ATTACK_ANGLE) { HandlePerformAttack(tg, dmgEvent); dmgEvent.killingBlow = false; numTargets--; } } } } void CMeleeAttackEvent::HandlePerformAttack(CWeenieObject *target, DamageEventData dmgEvent) { // okay, we're attacking. check for pvp interactions if (target->AsPlayer() && _weenie->AsPlayer()) { target->AsPlayer()->UpdatePKActivity(); _weenie->AsPlayer()->UpdatePKActivity(); } target->m_Qualities.SetInstanceID(CURRENT_ATTACKER_IID, _weenie->GetID()); uint32_t currentEnemy; if (target->m_Qualities.InqInstanceID(CURRENT_ENEMY_IID, currentEnemy)) { if(currentEnemy == 0) target->m_Qualities.SetInstanceID(CURRENT_ENEMY_IID, _weenie->GetID()); } if (_weenie->AsPlayer()) _weenie->AsPlayer()->CancelLifestoneProtection(); uint32_t meleeDefense = 0; if (target->InqSkill(MELEE_DEFENSE_SKILL, meleeDefense, FALSE) && meleeDefense > 0) { if (target->TryAttackEvade(dmgEvent.attackSkillLevel, STypeSkill::MELEE_DEFENSE_SKILL)) { target->OnEvadeAttack(_weenie); // send evasion message BinaryWriter attackerEvadeEvent; attackerEvadeEvent.Write(0x01B3); attackerEvadeEvent.WriteString(target->GetName()); _weenie->SendNetMessage(&attackerEvadeEvent, PRIVATE_MSG, TRUE, FALSE); BinaryWriter attackedEvadeEvent; attackedEvadeEvent.Write(0x01B4); attackedEvadeEvent.WriteString(_weenie->GetName()); target->SendNetMessage(&attackedEvadeEvent, PRIVATE_MSG, TRUE, FALSE); if (_weenie->m_Qualities.m_WeenieType == WeenieType::CombatPet_WeenieType && _weenie->AsMonster()->ShowCombatDamage()) { CPlayerWeenie* owner = g_pWorld->FindPlayer(_weenie->m_Qualities.GetIID(PET_OWNER_IID, 0)); if (owner && owner->AsPlayer()) { owner->AsPlayer()->SendText(csprintf("Your pet missed %s.", target->GetName().c_str()), LTT_COMBAT); } } if (_weenie && _weenie->AsPlayer()) _weenie->AsPlayer()->HandleAetheriaProc(0); return; } } DAMAGE_QUADRANT hitQuadrant = DAMAGE_QUADRANT::DQ_UNDEF; switch (_attack_height) { case HIGH_ATTACK_HEIGHT: hitQuadrant = DAMAGE_QUADRANT::DQ_HIGH; break; default: case MEDIUM_ATTACK_HEIGHT: hitQuadrant = DAMAGE_QUADRANT::DQ_MEDIUM; break; case LOW_ATTACK_HEIGHT: hitQuadrant = DAMAGE_QUADRANT::DQ_LOW; break; } double angle = _weenie->HeadingFrom(_target_id, false); if(angle >= 0 && angle < 180) hitQuadrant = (DAMAGE_QUADRANT)(hitQuadrant | DAMAGE_QUADRANT::DQ_RIGHT); else hitQuadrant = (DAMAGE_QUADRANT)(hitQuadrant | DAMAGE_QUADRANT::DQ_LEFT); if(angle >= 90 && angle < 270) hitQuadrant = (DAMAGE_QUADRANT)(hitQuadrant | DAMAGE_QUADRANT::DQ_BACK); else hitQuadrant = (DAMAGE_QUADRANT)(hitQuadrant | DAMAGE_QUADRANT::DQ_FRONT); dmgEvent.hit_quadrant = hitQuadrant; dmgEvent.target = target; CalculateCriticalHitData(&dmgEvent, NULL); dmgEvent.wasCrit = (Random::GenFloat(0.0, 1.0) < dmgEvent.critChance) ? true : false; if (dmgEvent.wasCrit) dmgEvent.baseDamage = dmgEvent.preVarianceDamage * (0.5 + _attack_power);//Calculate baseDamage with no variance (uses max dmg on weapon) else dmgEvent.baseDamage = dmgEvent.preVarianceDamage * (1.0f - Random::GenFloat(0.0f, dmgEvent.variance)) * (0.5 + _attack_power); // not a crit so include variance in base damage //cast on strike if (dmgEvent.weapon->InqDIDQuality(PROC_SPELL_DID, 0)) { double procChance = dmgEvent.weapon->InqFloatQuality(PROC_SPELL_RATE_FLOAT, 0.0f); bool proc = (Random::GenFloat(0.0, 1.0) < procChance) ? true : false; if (proc && target) { uint32_t targetid = target->GetID(); uint32_t procspell = dmgEvent.weapon->InqDIDQuality(PROC_SPELL_DID, 0); dmgEvent.weapon->MakeSpellcastingManager()->CastSpellInstant(targetid, procspell); } } if(_weenie && _weenie->AsPlayer()) _weenie->AsPlayer()->HandleAetheriaProc(target->GetID()); uint32_t dirtyFighting = 0; if (_weenie->AsPlayer() && _weenie->InqSkill(DIRTY_FIGHTING_SKILL, dirtyFighting, FALSE) && dirtyFighting > 0) { double skillCalc = (double)dirtyFighting / (double)dmgEvent.attackSkillLevel; double chanceOfDfEffect = .25 * min(1.0, skillCalc); double procRoll = Random::GenFloat(0.0, 1.0); bool proc = (procRoll < chanceOfDfEffect) ? true : false; SKILL_ADVANCEMENT_CLASS sac = SKILL_ADVANCEMENT_CLASS::UNTRAINED_SKILL_ADVANCEMENT_CLASS; bool hasQuality = _weenie->m_Qualities.InqSkillAdvancementClass((STypeSkill)DIRTY_FIGHTING_SKILL, sac); uint32_t targetid = target->GetID(); //_weenie->AsPlayer()->SendText(csprintf("Attack Skill: %u DF Skill: %u Skill Calc: %f Proc %f", dmgEvent.attackSkillLevel, dirtyFighting, skillCalc, procRoll), LTT_COMBAT); if (proc && sac >= TRAINED_SKILL_ADVANCEMENT_CLASS) { if (dmgEvent.hit_quadrant & DAMAGE_QUADRANT::DQ_HIGH) { if (sac == SPECIALIZED_SKILL_ADVANCEMENT_CLASS) { dmgEvent.weapon->MakeSpellcastingManager()->CastSpellInstant(targetid, 5938); //Blinding Assault dmgEvent.weapon->MakeSpellcastingManager()->CastSpellInstant(targetid, 5941); //Traumatic Assault } else { dmgEvent.weapon->MakeSpellcastingManager()->CastSpellInstant(targetid, 5942); //Blinding Blow dmgEvent.weapon->MakeSpellcastingManager()->CastSpellInstant(targetid, 5945); //Traumatic Blow } } else if (dmgEvent.hit_quadrant & DAMAGE_QUADRANT::DQ_MEDIUM) { if (sac == SPECIALIZED_SKILL_ADVANCEMENT_CLASS) { dmgEvent.source->MakeSpellcastingManager()->CastSpellInstant(targetid, 5939); //dmgEvent.weapon->TryCastSpell(targetid, 5939); //Bleeding Assault } else { dmgEvent.source->MakeSpellcastingManager()->CastSpellInstant(targetid, 5943); //dmgEvent.weapon->TryCastSpell(targetid, 5943); //Bleeding Blow } } else if (dmgEvent.hit_quadrant & DAMAGE_QUADRANT::DQ_LOW) { if (sac == SPECIALIZED_SKILL_ADVANCEMENT_CLASS) { dmgEvent.weapon->MakeSpellcastingManager()->CastSpellInstant(targetid, 5940); //Unbalancing Assault } else { dmgEvent.weapon->MakeSpellcastingManager()->CastSpellInstant(targetid, 5944); //Unbalancing Blow } } else { SERVER_ERROR << "Unknown error on Dirty Fighting"; } } } CalculateAttackConditions(&dmgEvent, _attack_power, angle); CalculateDamage(&dmgEvent); //if (dmgEvent.source->m_Qualities.m_WeenieType == CombatPet_WeenieType) //{ // CWeenieObject* owner = g_pWorld->FindPlayer(dmgEvent.source->m_Qualities.GetIID(PET_OWNER_IID, 0)); // if (owner) // { // owner->AsPlayer()->SendText(csprintf("Your Pet does %u damage to %s.", (uint32_t)ceil(dmgEvent.damageAfterMitigation), dmgEvent.target->GetName().c_str()), LTT_COMBAT); // } //} _weenie->TryToDealDamage(dmgEvent); }