PDA

View Full Version : multiple fixes to common damage and death


AiliaMorisato
01-23-2008, 01:03 PM
common damage from attack.cpp
Changes:
Moved death and other checks to the bottom to allow proper damage message generation in the case of the damage still killing the mob.
Made hits that dealt no damage to not interrupt spells.

got knocked down to 2nd post due to length

next, fixing the death struct, need to update all instances of it to my knowledge.

struct Death_Struct
{
/*000*/ int32 spawn_id;
/*004*/ int32 killer_id;
/*008*/ int32 corpseid; // was corpseid
/*012*/ int32 bindzoneid; // was type -- was attack_skill
/*016*/ int32 spell_id;
/*020*/ int32 attack_skill; //was bindzoneid -- switched by Ailia
/*024*/ int32 damage;
/*028*/ int32 unknown028;
};


now preventing the client from displaying damage message from the death packet it was sent since the server is generating the damage message instead.


void NPC::Death(Mob* other, sint32 damage, int16 spell, SkillType attack_skill) {

mlog(COMBAT__HITS, "Fatal blow dealt by %s with %d damage, spell %d, skill %d", other->GetName(), damage, spell, attack_skill);

if (this->IsEngaged())
{
zone->DelAggroMob();
#if EQDEBUG >= 11
LogFile->write(EQEMuLog::Debug,"NPC::Death() Mobs currently Aggro %i", zone->MobsAggroCount());
#endif
}
SetHP(0);
SetPet(0);
Mob* killer = GetHateDamageTop(this);

entity_list.RemoveFromTargets(this);

if(p_depop == true)
return;

BuffFadeAll();

EQApplicationPacket* app= new EQApplicationPacket(OP_Death,sizeof(Death_Struct)) ;
Death_Struct* d = (Death_Struct*)app->pBuffer;
d->spawn_id = GetID();
d->killer_id = other ? other->GetID() : 0;
// d->unknown12 = 1;
d->bindzoneid = 0;
d->spell_id = spell == SPELL_UNKNOWN ? 0xffffffff : spell;
d->attack_skill = 231; // prevent client from displaying extra hit message
d->damage = damage;
app->priority = 6;
entity_list.QueueClients(other, app, false);


and again for client death

void Client::Death(Mob* other, sint32 damage, int16 spell, SkillType attack_skill)
{
if(dead)
return; //cant die more than once...
int exploss;

mlog(COMBAT__HITS, "Fatal blow dealt by %s with %d damage, spell %d, skill %d", other->GetName(), damage, spell, attack_skill);

//
// #1: Send death packet to everyone
//

if(!spell) spell = SPELL_UNKNOWN;

SendLogoutPackets();

//make our become corpse packet, and queue to ourself before OP_Death.
EQApplicationPacket app2(OP_BecomeCorpse, sizeof(BecomeCorpse_Struct));
BecomeCorpse_Struct* bc = (BecomeCorpse_Struct*)app2.pBuffer;
bc->spawn_id = GetID();
bc->x = GetX();
bc->y = GetY();
bc->z = GetZ();
QueuePacket(&app2);

// make death packet
EQApplicationPacket app(OP_Death, sizeof(Death_Struct));
Death_Struct* d = (Death_Struct*)app.pBuffer;
d->spawn_id = GetID();
d->killer_id = other ? other->GetID() : 0;
//d->unknown12 = 1;
d->bindzoneid = m_pp.binds[0].zoneId;
d->spell_id = spell == SPELL_UNKNOWN ? 0xffffffff : spell;
d->attack_skill = 231;
d->damage = damage;
app.priority = 6;
entity_list.QueueClients(this, &app);

AiliaMorisato
01-23-2008, 01:04 PM
void Mob::CommonDamage(Mob* attacker, sint32 &damage, const int16 spell_id, const SkillType skill_used, bool &avoidable, const sint8 buffslot, const bool iBuffTic) {

mlog(COMBAT__HITS, "Applying damage %d done by %s with skill %d and spell %d, avoidable? %s, is %sa buff tic in slot %d",
damage, attacker?attacker->GetName():"NOBODY", skill_used, spell_id, avoidable?"yes":"no", iBuffTic?"":"not ", buffslot);

if (GetInvul() || DivineAura()) {
mlog(COMBAT__DAMAGE, "Avoiding %d damage due to invulnerability.", damage);
damage = -5;
}

if( spell_id != SPELL_UNKNOWN || attacker == NULL )
avoidable = false;

// only apply DS if physical damage (no spell damage)
// damage shield calls this function with spell_id set, so its unavoidable
if (attacker && damage > 0 && spell_id == SPELL_UNKNOWN) {
this->DamageShield(attacker);
for(int bs = 0; bs < BUFF_COUNT; bs++){
if(buffs[bs].numhits > 0 && !IsDiscipline(buffs[bs].spellid)){
if(buffs[bs].numhits == 1){
BuffFadeBySlot(bs, true);
}
else{
buffs[bs].numhits--;
}
}
}
}

if(attacker){
if(attacker->IsClient()){
if(!attacker->CastToClient()->GetFeigned())
AddToHateList(attacker, 0, damage, true, false, iBuffTic);
}
else
AddToHateList(attacker, 0, damage, true, false, iBuffTic);
}

if(damage > 0) {
//if there is some damage being done and theres an attacker involved
if(attacker) {
if(spell_id == SPELL_HARM_TOUCH2 && attacker->IsClient() && attacker->CastToClient()->CheckAAEffect(aaEffectLeechTouch)){
int healed = damage;
healed = attacker->GetActSpellHealing(spell_id, healed);
attacker->HealDamage(healed);
entity_list.MessageClose(this, true, 300, MT_Emote, "%s beams a smile at %s", attacker->GetCleanName(), this->GetCleanName() );
attacker->CastToClient()->DisableAAEffect(aaEffectLeechTouch);
}

// if spell is lifetap add hp to the caster
if (spell_id != SPELL_UNKNOWN && IsLifetapSpell( spell_id )) {
int healed = damage;
healed = attacker->GetActSpellHealing(spell_id, healed);
mlog(COMBAT__DAMAGE, "Applying lifetap heal of %d to %s", healed, attacker->GetName());
attacker->HealDamage(healed);

//we used to do a message to the client, but its gone now.
// emote goes with every one ... even npcs
entity_list.MessageClose(this, true, 300, MT_Emote, "%s beams a smile at %s", attacker->GetCleanName(), this->GetCleanName() );
}

// if we got a pet, thats not already fighting something send it into battle
Mob *pet = GetPet();
if (pet && !pet->IsFamiliar() && !pet->SpecAttacks[IMMUNE_AGGRO] && !pet->IsEngaged() && attacker != this)
{
mlog(PETS__AGGRO, "Sending pet %s into battle due to attack.", pet->GetName());
pet->AddToHateList(attacker, 1);
pet->SetTarget(attacker);
Message_StringID(10, PET_ATTACKING, pet->GetCleanName(), attacker->GetCleanName());
}
} //end `if there is some damage being done and theres anattacker person involved`

//see if any runes want to reduce this damage
if(spell_id == SPELL_UNKNOWN) {
damage = ReduceDamage(damage);
mlog(COMBAT__HITS, "Melee Damage reduced to %d", damage);
} else {
sint32 origdmg = damage;
damage = ReduceMagicalDamage(damage);
mlog(COMBAT__HITS, "Melee Damage reduced to %d", damage);
if (origdmg != damage && attacker && attacker->IsClient()) {
if(attacker->CastToClient()->GetFilter(FILTER_DAMAGESHIELD) != FilterHide)
attacker->Message(15, "The Spellshield absorbed %d of %d points of damage", origdmg - damage, origdmg);
}
}


if(IsClient() && CastToClient()->sneaking){
CastToClient()->sneaking = false;
SendAppearancePacket(AT_Sneak, 0);
}
if(attacker && attacker->IsClient() && attacker->CastToClient()->sneaking){
attacker->CastToClient()->sneaking = false;
attacker->SendAppearancePacket(AT_Sneak, 0);
}
//final damage has been determined.
} //end `if damage was done`

AiliaMorisato
01-23-2008, 01:05 PM
and the rest

//send damage packet...
if(!iBuffTic) { //buff ticks do not send damage, instead they just call SendHPUpdate(), which is done below
EQApplicationPacket* outapp = new EQApplicationPacket(OP_Damage, sizeof(CombatDamage_Struct));
CombatDamage_Struct* a = (CombatDamage_Struct*)outapp->pBuffer;
a->target = GetID();
if (attacker == NULL)
a->source = 0;
else if (attacker->IsClient() && attacker->CastToClient()->GMHideMe())
a->source = 0;
else
a->source = attacker->GetID();
a->type = SkillDamageTypes[skill_used]; // was 0x1c
a->damage = damage;
// if (attack_skill != 231)
// a->spellid = SPELL_UNKNOWN;
// else
a->spellid = spell_id;

//Note: if players can become pets, they will not receive damage messages of their own
//this was done to simplify the code here (since we can only effectively skip one mob on queue)
eqFilterType filter;
Mob *skip = attacker;
if(attacker && attacker->GetOwnerID()) {
//attacker is a pet, let pet owners see their pet's damage
Mob* owner = attacker->GetOwner();
if (owner && owner->IsClient()) {
if ((spell_id != SPELL_UNKNOWN) && damage>0) {
//special crap for spell damage, looks hackish to me
char val1[20]={0};
owner->Message_StringID(4,OTHER_HIT_NONMELEE,GetCleanName (),ConvertArray(damage,val1));
} else {
if(damage > 0) {
if(spell_id != SPELL_UNKNOWN)
filter = iBuffTic ? FilterDOT : FilterSpellDamage;
else
filter = FILTER_MYPETHITS;
} else if(damage == -5)
filter = FilterNone; //cant filter invulnerable
else
filter = FILTER_MYPETMISSES;
owner->CastToClient()->QueuePacket(outapp,true,CLIENT_CONNECTED,filter);
}
}
skip = owner;
} else {
//attacker is not a pet, send to the attacker

//if the attacker is a client, try them with the correct filter
if(attacker && attacker->IsClient()) {
if ((spell_id != SPELL_UNKNOWN) && damage>0) {
//special crap for spell damage, looks hackish to me
char val1[20]={0};
attacker->Message_StringID(4,OTHER_HIT_NONMELEE,GetCleanName (),ConvertArray(damage,val1));
} else {
if(damage > 0) {
if(spell_id != SPELL_UNKNOWN)
filter = iBuffTic ? FilterDOT : FilterSpellDamage;
else
filter = FilterNone; //cant filter our own hits
} else if(damage == -5)
filter = FilterNone; //cant filter invulnerable
else
filter = FILTER_MYMISSES;
attacker->CastToClient()->QueuePacket(outapp, true, CLIENT_CONNECTED, filter);
}
}
skip = attacker;
}

//send damage to all clients around except the specified skip mob (attacker or the attacker's owner) and ourself
if(damage > 0) {
if(spell_id != SPELL_UNKNOWN)
filter = iBuffTic ? FilterDOT : FilterSpellDamage;
else
filter = FILTER_OTHERHITS;
} else if(damage == -5)
filter = FilterNone; //cant filter invulnerable
else
filter = FILTER_OTHERMISSES;
//make attacker (the attacker) send the packet so we can skip them and the owner
//this call will send the packet to `this` as well (using the wrong filter) (will not happen until PC charm works)
//LogFile->write(EQEMuLog::Debug, "Queue damage to all except %s with filter %d (%d), type %d", skip->GetName(), filter, IsClient()?CastToClient()->GetFilter(filter):-1, a->type);
entity_list.QueueCloseClients(this, outapp, true, 200, skip, true, filter);

//send the damage to ourself if we are a client
if(IsClient()) {
//I dont think any filters apply to damage affecting us
CastToClient()->QueuePacket(outapp);
}

safe_delete(outapp);
} else {
//else, it is a buff tic...
// Everhood - So we can see our dot dmg like live shows it.
if(spell_id != SPELL_UNKNOWN && damage > 0 && attacker && attacker != this && attacker->IsClient()) {
//might filter on (attack_skill>200 && attack_skill<250), but I dont think we need it
if(attacker->CastToClient()->GetFilter(FilterDOT) != FilterHide) {
attacker->Message_StringID(MT_DoTDamage, OTHER_HIT_DOT, GetCleanName(),itoa(damage),spells[spell_id].name);
}
}
} //end packet sending

if(damage > 0) {
//check for death conditions
if(IsClient()) {
if((GetHP() - damage) <= -10) {
Death(attacker, damage, spell_id, skill_used);
return;
}
} else {
if (damage >= GetHP()) {
//killed...
SetHP(-100);
Death(attacker, damage, spell_id, skill_used);
return;
}
}

//not killed. Apply the damage
SetHP(GetHP() - damage);


//fade mez if we are mezzed
if (IsMezzed()) {
mlog(COMBAT__HITS, "Breaking mez due to attack.");
BuffFadeByEffect(SE_Mez);
}

//check stun chances if bashing
if ((skill_used == BASH || skill_used == KICK && (attacker && attacker->GetLevel() >= 55)) && GetLevel() < 56) {
int stun_resist = itembonuses.StunResist+spellbonuses.StunResist;
if(this->GetBaseRace() == OGRE && this->IsClient() && !attacker->BehindMob(this, attacker->GetX(), attacker->GetY())) {
mlog(COMBAT__HITS, "Stun Resisted. Ogres are immune to frontal melee stuns.");
} else {
if(stun_resist <= 0 || MakeRandomInt(0,99) >= stun_resist) {
mlog(COMBAT__HITS, "Stunned. We had %dpercent resist chance.");
Stun(0);
} else {
mlog(COMBAT__HITS, "Stun Resisted. We had %dpercent resist chance.");
}
}
}

if(spell_id != SPELL_UNKNOWN) {
//see if root will break
if (IsRooted()) { // neotoyko: only spells cancel root
if(GetAA(aaEnhancedRoot))
{
if (MakeRandomInt(0, 99) < 10) {
mlog(COMBAT__HITS, "Spell broke root! 10percent chance");
BuffFadeByEffect(SE_Root, buffslot); // buff slot is passed through so a root w/ dam doesnt cancel itself
} else {
mlog(COMBAT__HITS, "Spell did not break root. 10 percent chance");
}
}
else
{
if (MakeRandomInt(0, 99) < 20) {
mlog(COMBAT__HITS, "Spell broke root! 20percent chance");
BuffFadeByEffect(SE_Root, buffslot); // buff slot is passed through so a root w/ dam doesnt cancel itself
} else {
mlog(COMBAT__HITS, "Spell did not break root. 20 percent chance");
}
}
}
}
else{
//increment chances of interrupting
if(IsCasting() && damage > 0) { //shouldnt interrupt on regular spell damage
attacked_count++;
mlog(COMBAT__HITS, "Melee attack while casting. Attack count %d", attacked_count);
}
}

//send an HP update if we are hurt
if(GetHP() < GetMaxHP())
SendHPUpdate();
}
}

AiliaMorisato
01-23-2008, 01:11 PM
REALLY NEED THIS LOOKED AT --

Not sure if only the eq_packet_structs and Titanium struct are the only needing the values swapped, problem only verified on titanium client.

AiliaMorisato
01-23-2008, 01:18 PM
Damn 5 minute edit timer....

anyways, since my explanation was rather bad, the death struct changes fix the "you crush mobname for # damage" no matter how you killed it messages, that was even displaying from spells.

now no damage message is displayed by client when mob dies, so calling death routine will kill a mob with only the X was slain by X message, so no more "mobname tries to punch itself but misses" on #kill for example either.

moydock
01-23-2008, 03:00 PM
Very nice :) That has annoyed me for quite some time.

So_1337
02-26-2008, 09:09 AM
Was there any chance of this getting committed?

KLS
02-26-2008, 10:02 AM
Yeah I had it ready to be committed but lost my system so it sorta got set back, I'll get it done sooner or later >< sorry.

So_1337
02-26-2008, 01:40 PM
Doh! Was wondering where you'd been, hehe. Sorry to hear it, hope you were able to get up and running again without too much trouble.

KLS
03-04-2008, 08:59 PM
Alright poking back in, if this hasn't been put in yet I hope to have it or something similar in in a few days now that I can compile.