mirror of
https://github.com/therealKyp/Earth-and-Beyond-server
synced 2026-08-13 22:23:03 -04:00
509 lines
14 KiB
C++
509 lines
14 KiB
C++
// PlayerCombat.cpp
|
|
|
|
#include <float.h>
|
|
#include "PlayerClass.h"
|
|
#include "ServerManager.h"
|
|
#include "ObjectManager.h"
|
|
#include "Opcodes.h"
|
|
#include "PacketMethods.h"
|
|
|
|
void Player::ShipDamage(long source_id, float weapon_damage, short type)
|
|
{
|
|
if (!m_ObjectMgr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Object *obj = m_ObjectMgr->GetObjectFromID(source_id);
|
|
if (obj && !DebugPlayer())
|
|
{
|
|
float TakeDamage = 0;
|
|
float DamageAbsorbed = 0;
|
|
|
|
//terminate warp, prospecting
|
|
//TerminateWarp(); // This should only be done for hull damage
|
|
AbortProspecting(true, false);
|
|
|
|
if (m_DamageAbsorb > 0)
|
|
{
|
|
TakeDamage = m_DamageAbsorb - weapon_damage;
|
|
if (TakeDamage < 0)
|
|
{
|
|
// Take damage when we run out of absorb
|
|
m_DamageAbsorb = 0;
|
|
TakeDamage *= -1;
|
|
// Calculate the damage absorbed by the shield
|
|
DamageAbsorbed -= (weapon_damage - TakeDamage);
|
|
}
|
|
else
|
|
{
|
|
// We take no damage because of the shield
|
|
TakeDamage = 0;
|
|
m_DamageAbsorb -= weapon_damage;
|
|
// Calculate the damage absorbed by the shield
|
|
DamageAbsorbed -= weapon_damage;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No shield take all damage
|
|
TakeDamage = weapon_damage;
|
|
}
|
|
|
|
// TODO: Add code to reduce damage from buffs and deflects
|
|
|
|
/*****************
|
|
* Damage reduction section.
|
|
******************/
|
|
float redux_percent;
|
|
float BeforeDamage = TakeDamage;
|
|
switch(type)
|
|
{
|
|
case DAMAGE_IMPACT:
|
|
//is this needed?
|
|
redux_percent = m_Stats.GetStat(STAT_IMPACT_DEFLECT);
|
|
TakeDamage *= (1.0f - (redux_percent > 0.5f ? 0.5f : redux_percent));
|
|
break;
|
|
case DAMAGE_EXPLOSIVE:
|
|
redux_percent = m_Stats.GetStat(STAT_EXPLOSIVE_DEFLECT);
|
|
TakeDamage *= (1.0f - (redux_percent > 0.5f ? 0.5f : redux_percent));
|
|
break;
|
|
case DAMAGE_PLASMA:
|
|
redux_percent = m_Stats.GetStat(STAT_PLASMA_DEFLECT);
|
|
TakeDamage *= (1.0f - (redux_percent > 0.5f ? 0.5f : redux_percent));
|
|
break;
|
|
case DAMAGE_ENERGY:
|
|
redux_percent = m_Stats.GetStat(STAT_ENERGY_DEFLECT);
|
|
TakeDamage *= (1.0f - (redux_percent > 0.5f ? 0.5f : redux_percent));
|
|
break;
|
|
case DAMAGE_EMP:
|
|
redux_percent = m_Stats.GetStat(STAT_EMP_DEFLECT);
|
|
TakeDamage *= (1.0f - (redux_percent > 0.5f ? 0.5f : redux_percent));
|
|
break;
|
|
case DAMAGE_CHEMICAL:
|
|
redux_percent = m_Stats.GetStat(STAT_CHEM_DEFLECT);
|
|
TakeDamage *= (1.0f - (redux_percent > 0.5f ? 0.5f : redux_percent));
|
|
break;
|
|
}
|
|
|
|
// Find out if we absorbed any damage with the deflects
|
|
DamageAbsorbed -= (BeforeDamage - TakeDamage);
|
|
|
|
SendClientDamage(GameID(), source_id, TakeDamage, DamageAbsorbed, type, 1);
|
|
|
|
bool send_shield_hit = GetShieldValue() >= TakeDamage ? true : false;
|
|
if (send_shield_hit)
|
|
{
|
|
m_Mutex.Lock();
|
|
|
|
Player *p = (0);
|
|
|
|
while (g_PlayerMgr->GetNextPlayerOnList(p, m_RangeList))
|
|
{
|
|
p->SendObjectToObjectLinkedEffect(this, obj, 0x0021, 0x0003);
|
|
}
|
|
|
|
m_Mutex.Unlock();
|
|
}
|
|
|
|
//DB: Note - Shake does not occur on first hit
|
|
//Shake stops when you run out of shields as it expects hull to decrease then
|
|
ShipIndex()->Shake.SetDamage(TakeDamage);
|
|
|
|
float shield_level = GetShieldValue();
|
|
float dmg_reduce_percent = 0;
|
|
|
|
if(m_AbilityList[CLOAK]->InUse())
|
|
{
|
|
m_AbilityList[CLOAK]->InterruptSkillOnDamage(TakeDamage);
|
|
}
|
|
|
|
if (TakeDamage > shield_level)
|
|
{
|
|
float hull_dmg = TakeDamage - shield_level;
|
|
if (shield_level > 0.0f)
|
|
{
|
|
RemoveShield(shield_level);
|
|
}
|
|
|
|
//if dmg type is chemical, do bonus dmg to hull
|
|
if(type == DAMAGE_CHEMICAL)
|
|
{
|
|
hull_dmg *= 1.2f; //20% bonus damage
|
|
}
|
|
|
|
//calculate in damage control, TW/PW only
|
|
//Note: This is normal %, if you want the "effective percent" (like Turbo uses) you need to change this.
|
|
if ( (Profession() == PROFESSION_WARRIOR && Race() == RACE_PROGEN) ||
|
|
(Profession() == PROFESSION_WARRIOR && Race() == RACE_TERRAN) )
|
|
{
|
|
dmg_reduce_percent = 1.0f - m_Stats.GetStat(STAT_DAMAGE_CONTROL);
|
|
if(dmg_reduce_percent < 0)
|
|
{
|
|
dmg_reduce_percent = 0;
|
|
}
|
|
hull_dmg = hull_dmg * dmg_reduce_percent;
|
|
}
|
|
|
|
//if dmg type is EMP, take no damage to hull (EMP can't hurt the hull)
|
|
if(type == DAMAGE_EMP)
|
|
{
|
|
hull_dmg = 0;
|
|
}
|
|
|
|
RemoveHull(hull_dmg, obj);
|
|
TerminateWarp();
|
|
}
|
|
else
|
|
{
|
|
//bonus damage for plasma
|
|
if(type == DAMAGE_PLASMA)
|
|
{
|
|
TakeDamage *= 1.2f; //20% bonus damage
|
|
}
|
|
RemoveShield(TakeDamage);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Player::RemoveHull(float hull_dmg, Object *mob)
|
|
{
|
|
float hull = ShipIndex()->GetHullPoints() - hull_dmg;
|
|
|
|
ShipIndex()->SetHullPoints(hull);
|
|
|
|
//If we have used all of the hull, the player goes BOOM!
|
|
if (hull <= 0.0f)
|
|
{
|
|
ShipIndex()->SetHullPoints(0);
|
|
ShipIndex()->SetIsIncapacitated(true);
|
|
|
|
// Break formation/Leave Formation if in group
|
|
if (GroupID() != -1)
|
|
{
|
|
g_PlayerMgr->BreakFormation(GameID());
|
|
g_PlayerMgr->LeaveFormation(GameID());
|
|
}
|
|
|
|
// Stop regen
|
|
RemoveEnergy(0);
|
|
ShipIndex()->Shield.SetStartValue(GetShield());
|
|
ShipIndex()->Shield.SetEndTime(GetNet7TickCount()); // Set end time now!
|
|
|
|
ImmobilisePlayer();
|
|
TerminateWarp(); //This sends the packet (TODO: Remove packet send from here)
|
|
|
|
// Send Data
|
|
SendAuxShip();
|
|
|
|
//now deselect player's target - this causes the immobilisation to occur
|
|
UnSetTarget(-1);
|
|
|
|
//Stop any mobs from attacking
|
|
ObjectManager *om = GetObjectManager();
|
|
if (om) om->RemovePlayerFromMOBRangeLists(this);
|
|
if (mob && mob->HostilityTarget() == this->GameID())
|
|
{
|
|
mob->LostTarget(this);
|
|
}
|
|
|
|
m_Mutex.Lock();
|
|
|
|
Player *p = (0);
|
|
|
|
while (g_PlayerMgr->GetNextPlayerOnList(p, m_RangeList))
|
|
{
|
|
p->PointEffect(Position(), 1017);
|
|
}
|
|
|
|
m_Mutex.Unlock();
|
|
}
|
|
}
|
|
|
|
void Player::ImmobilisePlayer()
|
|
{
|
|
ShipIndex()->SetLockOrient(true);
|
|
ShipIndex()->SetLockSpeed(true);
|
|
ShipIndex()->SetEngineThrustState(0);
|
|
}
|
|
|
|
void Player::RemobilisePlayer()
|
|
{
|
|
ShipIndex()->SetLockOrient(false);
|
|
ShipIndex()->SetLockSpeed(false);
|
|
}
|
|
|
|
void Player::AttackMusicUpdate(bool update, long mob_id)
|
|
{
|
|
if (update)
|
|
{
|
|
m_AttackerCount++;
|
|
SendAttackerUpdates(ntohl(mob_id), 1);
|
|
}
|
|
else
|
|
{
|
|
m_AttackerCount--;
|
|
SendAttackerUpdates(ntohl(mob_id), 0);
|
|
|
|
if (m_AttackerCount < 0)
|
|
{
|
|
LogMessage("**** ERROR *: Attack count negative for %s\n", Name());
|
|
}
|
|
}
|
|
}
|
|
|
|
void Player::FireAllWeapons()
|
|
{
|
|
if (ShipIndex()->GetTargetGameID() > 0)
|
|
{
|
|
for (int i=0; i<6; i++)
|
|
{
|
|
m_Equip[i+3].ManualActivate();
|
|
}
|
|
SendPacketCache();
|
|
}
|
|
}
|
|
|
|
bool Player::FireEnergyCannon(ItemInstance *item)
|
|
{
|
|
float pos[3];
|
|
float *_heading = Heading();
|
|
float range = (float)item->WeaponRange;
|
|
|
|
if (GetEnergyValue() < item->EnergyUse)
|
|
{
|
|
SendVaMessage("Not enough energy! Need: %f", item->EnergyUse);
|
|
return false;
|
|
}
|
|
|
|
/* Use the energy */
|
|
RemoveEnergy(item->EnergyUse);
|
|
|
|
pos[0] = PosX() + ( range * _heading[0] );
|
|
pos[1] = PosY() + ( range * _heading[1] );
|
|
pos[2] = PosZ() + ( range * _heading[2] );
|
|
|
|
RangeListVec::iterator itrRList;
|
|
Player *p = (0);
|
|
|
|
while (g_PlayerMgr->GetNextPlayerOnList(p, m_RangeList))
|
|
{
|
|
p->PointEffect(pos, 1017, 3.0f);
|
|
//see if this player is in range of explosion.
|
|
float range_to_blast = p->RangeFrom(pos, true);
|
|
if (p != this)
|
|
{
|
|
SendVaMessage("Range to blast: %.2f", range_to_blast);
|
|
}
|
|
if (range_to_blast < 1000.0f)
|
|
{
|
|
float damage = p->CaughtInEnergyBlast(GameID(), range_to_blast);
|
|
if (damage > 0)
|
|
{
|
|
SendVaMessage("Blast Damage: %.2f", damage);
|
|
SendClientDamage(p->GameID(), GameID(), damage, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
float Player::CaughtInEnergyBlast(long source_id, float blast_range)
|
|
{
|
|
float blast_damage = 1.0f;
|
|
if (blast_range < 750.0f)
|
|
{
|
|
blast_damage = 5.0f;
|
|
}
|
|
if (blast_range < 500.0f)
|
|
{
|
|
blast_damage = 10.0f;
|
|
}
|
|
if (blast_range < 400.0f)
|
|
{
|
|
blast_damage = 15.0f;
|
|
}
|
|
if (blast_range < 300.0f)
|
|
{
|
|
blast_damage = 20.0f;
|
|
}
|
|
if (blast_range < 200.0f)
|
|
{
|
|
blast_damage = 30.0f;
|
|
}
|
|
if (blast_range < 100.0f)
|
|
{
|
|
blast_damage = 75.0f;
|
|
}
|
|
if (blast_range < 50.0f)
|
|
{
|
|
blast_damage = 200.0f;
|
|
}
|
|
|
|
ShipDamage(source_id, blast_damage, 0);
|
|
|
|
return (blast_damage);
|
|
}
|
|
|
|
float Player::CalcDamage(int weapon_damage, int subcat, bool *critical)
|
|
{
|
|
const short FRACTION_MULTIPLIER = 10000; //This value determines how many decimals of a percent are kept for rolling damage tables.
|
|
const short FRACTION_FIXUP = 100;
|
|
float damage_bonus;
|
|
float damage_inflicted;
|
|
short critical_chance = 5 * FRACTION_FIXUP; //5%
|
|
short miss_chance = 5 * FRACTION_FIXUP; //5% , miss_chance of 0 = never miss.
|
|
|
|
//This calculates a different crit value for any class that should have critical targeting.
|
|
critical_chance += (short)
|
|
( (m_Stats.GetStatType(STAT_CRITICAL_RATE, STAT_BUFF_MULT)*FRACTION_MULTIPLIER) +
|
|
(m_Stats.GetStatType(STAT_CRITICAL_RATE, STAT_BUFF_VALUE)*FRACTION_FIXUP) );
|
|
|
|
if(FRACTION_MULTIPLIER-critical_chance < 0)
|
|
{
|
|
critical_chance = 0; //100% crits, when you hit.
|
|
}
|
|
|
|
//TO-DO: compute miss_chance based on weapon skill + buffs
|
|
|
|
// Get Damage bonus by weapon type
|
|
switch(subcat)
|
|
{
|
|
case 100: // Beam
|
|
damage_bonus = 1.0f + m_Stats.GetStatType(STAT_BEAM_DAMAGE, STAT_BUFF_MULT);
|
|
|
|
//can't call CalculateStat because STAT_BASE_VALUE = 0, and so any % modifiers would be lost
|
|
miss_chance -= (short)
|
|
( (m_Stats.GetStatType(STAT_BEAM_ACCURACY, STAT_BUFF_MULT)*1000) +
|
|
(m_Stats.GetStatType(STAT_BEAM_ACCURACY, STAT_BUFF_VALUE)*FRACTION_FIXUP) );
|
|
break;
|
|
|
|
case 101: // Projectile
|
|
damage_bonus = 1.0f + m_Stats.GetStatType(STAT_PROJECTILES_DAMAGE, STAT_BUFF_MULT);
|
|
|
|
//can't call CalculateStat because STAT_BASE_VALUE = 0, and so any % modifiers would be lost
|
|
miss_chance -= (short)
|
|
( (m_Stats.GetStatType(STAT_PROJECTILES_ACCURACY, STAT_BUFF_MULT)*1000) +
|
|
(m_Stats.GetStatType(STAT_PROJECTILES_ACCURACY, STAT_BUFF_VALUE)*FRACTION_FIXUP) );
|
|
break;
|
|
|
|
case 102: // Missiles
|
|
damage_bonus = 1.0f + m_Stats.GetStatType(STAT_MISSILE_DAMAGE, STAT_BUFF_MULT);
|
|
|
|
//can't call CalculateStat because STAT_BASE_VALUE = 0, and so any % modifiers would be lost
|
|
miss_chance -= (short)
|
|
( (m_Stats.GetStatType(STAT_MISSILE_ACCURACY, STAT_BUFF_MULT)*1000) +
|
|
(m_Stats.GetStatType(STAT_MISSILE_ACCURACY, STAT_BUFF_VALUE)*FRACTION_FIXUP) );
|
|
break;
|
|
|
|
default:
|
|
LogMessage("ERROR: Weapon subcategory [%d] wrong for %s\n", subcat, Name());
|
|
damage_bonus = 0.0f;
|
|
break;
|
|
}
|
|
|
|
//find the base weapon damage, first have we got a critical or missed?
|
|
short to_hit = (rand() % FRACTION_MULTIPLIER) + 1;
|
|
|
|
float damage_fraction = 1.0f;
|
|
|
|
//This system rolls a number, and then determines which category of damage it falls into.
|
|
//It is still possible to miss with a 100% crit rate. It is possible to miss on a crit.
|
|
if (to_hit <= miss_chance)
|
|
{
|
|
//Weapon missed, damage zero
|
|
damage_bonus = 0;
|
|
}
|
|
else if (to_hit >= FRACTION_MULTIPLIER-critical_chance)
|
|
{
|
|
//Critical!! Double damage
|
|
damage_bonus = damage_bonus * 2.0f;
|
|
*critical = true;
|
|
}
|
|
//IMPLIED else: did normal damage.
|
|
|
|
//TO-DO: Put in fractional damage FOR BEAMS ONLY, and also DOTs for Chemical/Plasma damage.
|
|
|
|
//Now calculate the damage based on weapon base damage and bonus.
|
|
damage_inflicted = damage_fraction * (float)(weapon_damage) * damage_bonus;
|
|
|
|
return (damage_inflicted);
|
|
}
|
|
|
|
static const char *mount_names[] =
|
|
{
|
|
"~WEAP_06", // 0 nose mount 1 (progen only)
|
|
"~WEAP_05", // 1 nose mount 2 (All other races, centre).
|
|
"~02/~WEAP_06", // 2 wing mounts
|
|
"~02/~WEAP_05", // 3
|
|
"~02/~WEAP_04", // 4
|
|
"~02/~WEAP_03", // 5
|
|
"~02/~WEAP_02", // 6
|
|
"~02/~WEAP_01", // 7
|
|
"~01/~WEAP_02", // 8 pod mounts for Progen Warriors (on the ends of the pylons).
|
|
"~01/~WEAP_01" // 9
|
|
};
|
|
|
|
static char MountNameIndexes[] = //choose names from above table
|
|
{
|
|
1, 4, 5, 6, 7,-1, // TW
|
|
1, 4, 5, 6,-1,-1, // TT
|
|
4, 5, 6, 7,-1,-1, // TE
|
|
1, 4, 5, 6, 7,-1, // JW
|
|
4, 5, 6, 7,-1,-1, // JT
|
|
1, 4, 5,-1,-1,-1, // JE
|
|
9, 8, 4, 5, 2, 3, // PW
|
|
2, 3, 4, 5,-1,-1, // PT
|
|
0, 1, 4, 5,-1,-1 // PE
|
|
};
|
|
|
|
static char PEAltMounts[] =
|
|
{
|
|
0, 1, 4, 5,-1,-1, // PE hull 1
|
|
2, 3, 4, 5,-1,-1,
|
|
0, 1, 4, 5,-1,-1
|
|
};
|
|
|
|
void Player::AddWeapon(long weapon_id) //weapon 1 to 6
|
|
{
|
|
const char *bonename;
|
|
if (MountNameIndexes[ClassIndex() * 6 + (weapon_id - 1)] == -1)
|
|
{
|
|
LogMessage("Error adding weapon for %s, class not permitted to have weapon #%d\n", Name(), weapon_id);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (ClassIndex() == 8) //TODO: put class indexes on an enum for clarity. 8 = PE
|
|
{
|
|
bonename = mount_names[PEAltMounts[m_Database.ship_data.hull * 6 + (weapon_id - 1)]];
|
|
}
|
|
else
|
|
{
|
|
bonename = mount_names[MountNameIndexes[ClassIndex() * 6 + (weapon_id - 1)]];
|
|
}
|
|
}
|
|
|
|
ShipIndex()->Inventory.Mounts.SetMount(2+weapon_id, WeaponMount);
|
|
ShipIndex()->Inventory.EquipInv.EquipItem[2+weapon_id].SetItemTemplateID(-1);
|
|
ShipIndex()->Inventory.MountBones.SetMountBoneName(2 + weapon_id, bonename);
|
|
|
|
m_WeaponSlots++;
|
|
}
|
|
|
|
void Player::ChangeMountBoneName(long weapon_id, char *mount_name)
|
|
{
|
|
ShipIndex()->Inventory.MountBones.SetMountBoneName(2 + weapon_id, mount_name);
|
|
}
|
|
|
|
void Player::NeatenUpWeaponMounts()
|
|
{
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
if (MountNameIndexes[ClassIndex() * 6 + i] != -1)
|
|
{
|
|
ShipIndex()->Inventory.MountBones.SetMountBoneName(3 + i, mount_names[MountNameIndexes[ClassIndex() * 6 + i]]);
|
|
}
|
|
}
|
|
}
|