trevius
09-01-2008, 06:33 AM
To make some more dynamic encounters, I thought it would be cool to have a way to adjust how hard a mob hits in real time. So, they could potentially get stronger (or weaker) as a fight progresses. I think the best way to do this would be via a quest command.
Maybe there is already a way to do this, but I can't seem to figure it out. If there isn't a way to do this, maybe someone could suggest a way to implement a new command code for quest objects. Basically, I would like to be able to add to or subtract X amount from every swing. I think the Damage quest object only does a 1 time set amount of damage, unless I don't fully understand how it works. But, something similar to that command that can adjust the set rate for every swing would be nice.
These are the related quest objects that I think could help for setting this:
GetMaxDamage(tlevel)
GetMaxDMG()
GetDamageAmount(tmob)
Damage(from, damage, spell_id, attack_skill, avoidable= true, buffslot= -1, iBuffTic= false)
And here is the source where I think the command would have to be added, but I am not completely sure yet:
attack.cpp
bool NPC::Attack(Mob* other, int Hand, bool bRiposte) // Kaiyodo - base function has changed prototype, need to update overloaded version
{
_ZP(NPC_Attack);
int damage = 0;
if (!other) {
SetTarget(NULL);
LogFile->write(EQEMuLog::Error, "A null Mob object was passed to NPC::Attack() for evaluation!");
return false;
}
if(DivineAura())
return(false);
if(!GetTarget())
SetTarget(other);
//Check that we can attack before we calc heading and face our target
if (!IsAttackAllowed(other)) {
if (this->GetOwnerID())
entity_list.MessageClose(this, 1, 200, 10, "%s says, 'That is not a legal target master.'", this->GetCleanName());
if(other)
RemoveFromHateList(other);
mlog(COMBAT__ATTACKS, "I am not allowed to attack %s", other->GetName());
return false;
}
float calcheading=CalculateHeadingToTarget(target->GetX(), target->GetY());
if((calcheading)!=GetHeading()){
SetHeading(calcheading);
FaceTarget(target, true);
}
if(!combat_event) {
mlog(COMBAT__HITS, "Triggering EVENT_COMBAT due to attack on %s", other->GetName());
parse->Event(EVENT_COMBAT, this->GetNPCTypeID(), "1", this, other);
combat_event = true;
}
combat_event_timer.Start(CombatEventTimer_expire);
SkillType skillinuse = HAND_TO_HAND;
//figure out what weapon they are using, if any
const Item_Struct* weapon = NULL;
if (Hand == 13 && equipment[7] > 0)
weapon = database.GetItem(equipment[7]);
else if (equipment[8])
weapon = database.GetItem(equipment[8]);
//We dont factor much from the weapon into the attack.
//Just the skill type so it doesn't look silly using punching animations and stuff while wielding weapons
if(weapon) {
mlog(COMBAT__ATTACKS, "Attacking with weapon: %s (%d) (too bad im not using it for much)", weapon->Name, weapon->ID);
if(Hand == 14 && weapon->ItemType == ItemTypeShield){
mlog(COMBAT__ATTACKS, "Attack with shield canceled.");
return false;
}
switch(weapon->ItemType){
case ItemType1HS:
skillinuse = _1H_SLASHING;
break;
case ItemType2HS:
skillinuse = _2H_SLASHING;
break;
case ItemTypePierce:
case ItemType2HPierce:
skillinuse = PIERCING;
break;
case ItemType1HB:
skillinuse = _1H_BLUNT;
break;
case ItemType2HB:
skillinuse = _2H_BLUNT;
break;
case ItemTypeBow:
skillinuse = ARCHERY;
break;
case ItemTypeThrowing:
case ItemTypeThrowingv2:
skillinuse = THROWING;
break;
default:
skillinuse = HAND_TO_HAND;
break;
}
}
int weapon_damage = GetWeaponDamage(other, weapon);
//do attack animation regardless of whether or not we can hit below
sint16 charges = 0;
ItemInst weapon_inst(weapon, charges);
AttackAnimation(skillinuse, Hand, &weapon_inst);
//basically "if not immune" then do the attack
if((weapon_damage) > 0) {
//ele and bane dmg too
//NPCs add this differently than PCs
//if NPCs can't inheriently hit the target we don't add bane/magic dmg which isn't exactly the same as PCs
int16 eleBane = 0;
if(weapon){
if(weapon->BaneDmgBody == other->GetBodyType()){
eleBane += weapon->BaneDmgAmt;
}
if(weapon->BaneDmgRace == other->GetRace()){
eleBane += weapon->BaneDmgRaceAmt;
}
if(weapon->ElemDmgAmt){
eleBane += (weapon->ElemDmgAmt * other->ResistSpell(weapon->ElemDmgType, 0, this) / 100);
}
}
if(!RuleB(NPC, UseItemBonusesForNonPets)){
if(!GetOwner()){
eleBane = 0;
}
}
int8 otherlevel = other->GetLevel();
int8 mylevel = this->GetLevel();
otherlevel = otherlevel ? otherlevel : 1;
mylevel = mylevel ? mylevel : 1;
//instead of calcing damage in floats lets just go straight to ints
if(RuleB(Combat, UseIntervalAC))
damage = (max_dmg+eleBane);
else
damage = MakeRandomInt((min_dmg+eleBane),(max_dmg+eleBane)) ;
//check if we're hitting above our max or below it.
if((min_dmg+eleBane) != 0 && damage < (min_dmg+eleBane)) {
mlog(COMBAT__DAMAGE, "Damage (%d) is below min (%d). Setting to min.", damage, (min_dmg+eleBane));
damage = (min_dmg+eleBane);
}
if((max_dmg+eleBane) != 0 && damage > (max_dmg+eleBane)) {
mlog(COMBAT__DAMAGE, "Damage (%d) is above max (%d). Setting to max.", damage, (max_dmg+eleBane));
damage = (max_dmg+eleBane);
}
sint32 hate = damage;
//THIS IS WHERE WE CHECK TO SEE IF WE HIT:
if(other->IsClient() && other->CastToClient()->IsSitting()) {
mlog(COMBAT__DAMAGE, "Client %s is sitting. Hitting for max damage (%d).", other->GetName(), (max_dmg+eleBane));
damage = (max_dmg+eleBane);
mlog(COMBAT__HITS, "Generating hate %d towards %s", hate, GetName());
// now add done damage to the hate list
other->AddToHateList(this, hate);
} else {
if(!other->CheckHitChance(this, skillinuse, Hand)) {
damage = 0; //miss
} else { //hit, check for damage avoidance
other->AvoidDamage(this, damage);
other->MeleeMitigation(this, damage, min_dmg+eleBane);
ApplyMeleeDamageBonus(skillinuse, damage);
TryCriticalHit(other, skillinuse, damage);
mlog(COMBAT__HITS, "Generating hate %d towards %s", hate, GetName());
// now add done damage to the hate list
if(damage != 0)
other->AddToHateList(this, hate);
else
other->AddToHateList(this, 0);
}
}
mlog(COMBAT__DAMAGE, "Final damage against %s: %d", other->GetName(), damage);
if(other->IsClient() && IsPet() && GetOwner()->IsClient()) {
//pets do half damage to clients in pvp
damage=damage/2;
}
}
else
damage = -5;
//cant riposte a riposte
if (bRiposte && damage == -3) {
mlog(COMBAT__DAMAGE, "Riposte of riposte canceled.");
return false;
}
if(GetHP() > 0 && other->GetHP() >= -11) {
other->Damage(this, damage, SPELL_UNKNOWN, skillinuse, false); // Not avoidable client already had thier chance to Avoid
}
//break invis when you attack
if(invisible) {
mlog(COMBAT__ATTACKS, "Removing invisibility due to melee attack.");
BuffFadeByEffect(SE_Invisibility);
BuffFadeByEffect(SE_Invisibility2);
invisible = false;
}
if(invisible_undead) {
mlog(COMBAT__ATTACKS, "Removing invisibility vs. undead due to melee attack.");
BuffFadeByEffect(SE_InvisVsUndead);
BuffFadeByEffect(SE_InvisVsUndead2);
invisible_undead = false;
}
if(invisible_animals){
mlog(COMBAT__ATTACKS, "Removing invisibility vs. animals due to melee attack.");
BuffFadeByEffect(SE_InvisVsAnimals);
invisible_animals = false;
}
if(hidden || improved_hidden)
{
EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct));
SpawnAppearance_Struct* sa_out = (SpawnAppearance_Struct*)outapp->pBuffer;
sa_out->spawn_id = GetID();
sa_out->type = 0x03;
sa_out->parameter = 0;
entity_list.QueueClients(this, outapp, true);
safe_delete(outapp);
}
hidden = false;
improved_hidden = false;
//I doubt this works...
if (!target)
return true; //We killed them
// Kaiyodo - Check for proc on weapon based on DEX
if( !bRiposte && other->GetHP() > 0 ) {
TryWeaponProc(weapon, other); //no weapon
}
// now check ripostes
if (damage == -3) { // riposting
DoRiposte(other);
}
if (damage > 0)
return true;
else
return false;
}
void NPC::Damage(Mob* other, sint32 damage, int16 spell_id, SkillType attack_skill, bool avoidable, sint8 buffslot, bool iBuffTic) {
if(spell_id==0)
spell_id = SPELL_UNKNOWN;
//handle EVENT_ATTACK. Resets after we have not been attacked for 12 seconds
if(!attack_event) {
mlog(COMBAT__HITS, "Triggering EVENT_ATTACK due to attack by %s", other->GetName());
parse->Event(EVENT_ATTACK, this->GetNPCTypeID(), 0, this, other);
attack_event = true;
}
if(!combat_event) {
mlog(COMBAT__HITS, "Triggering EVENT_COMBAT due to attack by %s", other->GetName());
parse->Event(EVENT_COMBAT, this->GetNPCTypeID(), "1", this, other);
combat_event = true;
}
attacked_timer.Start(CombatEventTimer_expire - 1); //-1 to solidify an assumption in NPC::Process
combat_event_timer.Start(CombatEventTimer_expire);
if (!IsEngaged())
zone->AddAggroMob();
//do a majority of the work...
CommonDamage(other, damage, spell_id, attack_skill, avoidable, buffslot, iBuffTic);
if(damage > 0) {
//see if we are gunna start fleeing
if(!IsPet()) CheckFlee();
}
}
This is probably more for custom servers than anything, but I do think it would add more dynamics to some encounters. I am open to ideas if anyone has any :)
Maybe there is already a way to do this, but I can't seem to figure it out. If there isn't a way to do this, maybe someone could suggest a way to implement a new command code for quest objects. Basically, I would like to be able to add to or subtract X amount from every swing. I think the Damage quest object only does a 1 time set amount of damage, unless I don't fully understand how it works. But, something similar to that command that can adjust the set rate for every swing would be nice.
These are the related quest objects that I think could help for setting this:
GetMaxDamage(tlevel)
GetMaxDMG()
GetDamageAmount(tmob)
Damage(from, damage, spell_id, attack_skill, avoidable= true, buffslot= -1, iBuffTic= false)
And here is the source where I think the command would have to be added, but I am not completely sure yet:
attack.cpp
bool NPC::Attack(Mob* other, int Hand, bool bRiposte) // Kaiyodo - base function has changed prototype, need to update overloaded version
{
_ZP(NPC_Attack);
int damage = 0;
if (!other) {
SetTarget(NULL);
LogFile->write(EQEMuLog::Error, "A null Mob object was passed to NPC::Attack() for evaluation!");
return false;
}
if(DivineAura())
return(false);
if(!GetTarget())
SetTarget(other);
//Check that we can attack before we calc heading and face our target
if (!IsAttackAllowed(other)) {
if (this->GetOwnerID())
entity_list.MessageClose(this, 1, 200, 10, "%s says, 'That is not a legal target master.'", this->GetCleanName());
if(other)
RemoveFromHateList(other);
mlog(COMBAT__ATTACKS, "I am not allowed to attack %s", other->GetName());
return false;
}
float calcheading=CalculateHeadingToTarget(target->GetX(), target->GetY());
if((calcheading)!=GetHeading()){
SetHeading(calcheading);
FaceTarget(target, true);
}
if(!combat_event) {
mlog(COMBAT__HITS, "Triggering EVENT_COMBAT due to attack on %s", other->GetName());
parse->Event(EVENT_COMBAT, this->GetNPCTypeID(), "1", this, other);
combat_event = true;
}
combat_event_timer.Start(CombatEventTimer_expire);
SkillType skillinuse = HAND_TO_HAND;
//figure out what weapon they are using, if any
const Item_Struct* weapon = NULL;
if (Hand == 13 && equipment[7] > 0)
weapon = database.GetItem(equipment[7]);
else if (equipment[8])
weapon = database.GetItem(equipment[8]);
//We dont factor much from the weapon into the attack.
//Just the skill type so it doesn't look silly using punching animations and stuff while wielding weapons
if(weapon) {
mlog(COMBAT__ATTACKS, "Attacking with weapon: %s (%d) (too bad im not using it for much)", weapon->Name, weapon->ID);
if(Hand == 14 && weapon->ItemType == ItemTypeShield){
mlog(COMBAT__ATTACKS, "Attack with shield canceled.");
return false;
}
switch(weapon->ItemType){
case ItemType1HS:
skillinuse = _1H_SLASHING;
break;
case ItemType2HS:
skillinuse = _2H_SLASHING;
break;
case ItemTypePierce:
case ItemType2HPierce:
skillinuse = PIERCING;
break;
case ItemType1HB:
skillinuse = _1H_BLUNT;
break;
case ItemType2HB:
skillinuse = _2H_BLUNT;
break;
case ItemTypeBow:
skillinuse = ARCHERY;
break;
case ItemTypeThrowing:
case ItemTypeThrowingv2:
skillinuse = THROWING;
break;
default:
skillinuse = HAND_TO_HAND;
break;
}
}
int weapon_damage = GetWeaponDamage(other, weapon);
//do attack animation regardless of whether or not we can hit below
sint16 charges = 0;
ItemInst weapon_inst(weapon, charges);
AttackAnimation(skillinuse, Hand, &weapon_inst);
//basically "if not immune" then do the attack
if((weapon_damage) > 0) {
//ele and bane dmg too
//NPCs add this differently than PCs
//if NPCs can't inheriently hit the target we don't add bane/magic dmg which isn't exactly the same as PCs
int16 eleBane = 0;
if(weapon){
if(weapon->BaneDmgBody == other->GetBodyType()){
eleBane += weapon->BaneDmgAmt;
}
if(weapon->BaneDmgRace == other->GetRace()){
eleBane += weapon->BaneDmgRaceAmt;
}
if(weapon->ElemDmgAmt){
eleBane += (weapon->ElemDmgAmt * other->ResistSpell(weapon->ElemDmgType, 0, this) / 100);
}
}
if(!RuleB(NPC, UseItemBonusesForNonPets)){
if(!GetOwner()){
eleBane = 0;
}
}
int8 otherlevel = other->GetLevel();
int8 mylevel = this->GetLevel();
otherlevel = otherlevel ? otherlevel : 1;
mylevel = mylevel ? mylevel : 1;
//instead of calcing damage in floats lets just go straight to ints
if(RuleB(Combat, UseIntervalAC))
damage = (max_dmg+eleBane);
else
damage = MakeRandomInt((min_dmg+eleBane),(max_dmg+eleBane)) ;
//check if we're hitting above our max or below it.
if((min_dmg+eleBane) != 0 && damage < (min_dmg+eleBane)) {
mlog(COMBAT__DAMAGE, "Damage (%d) is below min (%d). Setting to min.", damage, (min_dmg+eleBane));
damage = (min_dmg+eleBane);
}
if((max_dmg+eleBane) != 0 && damage > (max_dmg+eleBane)) {
mlog(COMBAT__DAMAGE, "Damage (%d) is above max (%d). Setting to max.", damage, (max_dmg+eleBane));
damage = (max_dmg+eleBane);
}
sint32 hate = damage;
//THIS IS WHERE WE CHECK TO SEE IF WE HIT:
if(other->IsClient() && other->CastToClient()->IsSitting()) {
mlog(COMBAT__DAMAGE, "Client %s is sitting. Hitting for max damage (%d).", other->GetName(), (max_dmg+eleBane));
damage = (max_dmg+eleBane);
mlog(COMBAT__HITS, "Generating hate %d towards %s", hate, GetName());
// now add done damage to the hate list
other->AddToHateList(this, hate);
} else {
if(!other->CheckHitChance(this, skillinuse, Hand)) {
damage = 0; //miss
} else { //hit, check for damage avoidance
other->AvoidDamage(this, damage);
other->MeleeMitigation(this, damage, min_dmg+eleBane);
ApplyMeleeDamageBonus(skillinuse, damage);
TryCriticalHit(other, skillinuse, damage);
mlog(COMBAT__HITS, "Generating hate %d towards %s", hate, GetName());
// now add done damage to the hate list
if(damage != 0)
other->AddToHateList(this, hate);
else
other->AddToHateList(this, 0);
}
}
mlog(COMBAT__DAMAGE, "Final damage against %s: %d", other->GetName(), damage);
if(other->IsClient() && IsPet() && GetOwner()->IsClient()) {
//pets do half damage to clients in pvp
damage=damage/2;
}
}
else
damage = -5;
//cant riposte a riposte
if (bRiposte && damage == -3) {
mlog(COMBAT__DAMAGE, "Riposte of riposte canceled.");
return false;
}
if(GetHP() > 0 && other->GetHP() >= -11) {
other->Damage(this, damage, SPELL_UNKNOWN, skillinuse, false); // Not avoidable client already had thier chance to Avoid
}
//break invis when you attack
if(invisible) {
mlog(COMBAT__ATTACKS, "Removing invisibility due to melee attack.");
BuffFadeByEffect(SE_Invisibility);
BuffFadeByEffect(SE_Invisibility2);
invisible = false;
}
if(invisible_undead) {
mlog(COMBAT__ATTACKS, "Removing invisibility vs. undead due to melee attack.");
BuffFadeByEffect(SE_InvisVsUndead);
BuffFadeByEffect(SE_InvisVsUndead2);
invisible_undead = false;
}
if(invisible_animals){
mlog(COMBAT__ATTACKS, "Removing invisibility vs. animals due to melee attack.");
BuffFadeByEffect(SE_InvisVsAnimals);
invisible_animals = false;
}
if(hidden || improved_hidden)
{
EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct));
SpawnAppearance_Struct* sa_out = (SpawnAppearance_Struct*)outapp->pBuffer;
sa_out->spawn_id = GetID();
sa_out->type = 0x03;
sa_out->parameter = 0;
entity_list.QueueClients(this, outapp, true);
safe_delete(outapp);
}
hidden = false;
improved_hidden = false;
//I doubt this works...
if (!target)
return true; //We killed them
// Kaiyodo - Check for proc on weapon based on DEX
if( !bRiposte && other->GetHP() > 0 ) {
TryWeaponProc(weapon, other); //no weapon
}
// now check ripostes
if (damage == -3) { // riposting
DoRiposte(other);
}
if (damage > 0)
return true;
else
return false;
}
void NPC::Damage(Mob* other, sint32 damage, int16 spell_id, SkillType attack_skill, bool avoidable, sint8 buffslot, bool iBuffTic) {
if(spell_id==0)
spell_id = SPELL_UNKNOWN;
//handle EVENT_ATTACK. Resets after we have not been attacked for 12 seconds
if(!attack_event) {
mlog(COMBAT__HITS, "Triggering EVENT_ATTACK due to attack by %s", other->GetName());
parse->Event(EVENT_ATTACK, this->GetNPCTypeID(), 0, this, other);
attack_event = true;
}
if(!combat_event) {
mlog(COMBAT__HITS, "Triggering EVENT_COMBAT due to attack by %s", other->GetName());
parse->Event(EVENT_COMBAT, this->GetNPCTypeID(), "1", this, other);
combat_event = true;
}
attacked_timer.Start(CombatEventTimer_expire - 1); //-1 to solidify an assumption in NPC::Process
combat_event_timer.Start(CombatEventTimer_expire);
if (!IsEngaged())
zone->AddAggroMob();
//do a majority of the work...
CommonDamage(other, damage, spell_id, attack_skill, avoidable, buffslot, iBuffTic);
if(damage > 0) {
//see if we are gunna start fleeing
if(!IsPet()) CheckFlee();
}
}
This is probably more for custom servers than anything, but I do think it would add more dynamics to some encounters. I am open to ideas if anyone has any :)