gdle/Source/combat/MeleeAttackEventData.cpp
2020-09-27 15:09:30 +00:00

580 lines
18 KiB
C++

#include <StdAfx.h>
#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, &params);
}
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<CWeenieObject *> 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<uint32_t>(0x01B3);
attackerEvadeEvent.WriteString(target->GetName());
_weenie->SendNetMessage(&attackerEvadeEvent, PRIVATE_MSG, TRUE, FALSE);
BinaryWriter attackedEvadeEvent;
attackedEvadeEvent.Write<uint32_t>(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);
}