Go Back   EQEmulator Home > EQEmulator Forums > Development > Development::Feature Requests

Development::Feature Requests Post suggestions/feature requests here.

Reply
 
Thread Tools Display Modes
  #1  
Old 09-01-2008, 06:33 AM
trevius's Avatar
trevius
Developer
 
Join Date: Aug 2006
Location: USA
Posts: 5,946
Default Adjusting Mob Damage in Real Time

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:

Code:
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
Code:
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
__________________
Trevazar/Trevius Owner of: Storm Haven
Everquest Emulator FAQ (Frequently Asked Questions) - Read It!
Reply With Quote
  #2  
Old 09-01-2008, 06:43 AM
joligario's Avatar
joligario
Developer
 
Join Date: Mar 2003
Posts: 1,490
Default

Not necessarily just custom server. That is actually pretty relevant.

Example: Necromancer 1.5 Prequest. The Man-Eating Freshwater Shark is supposed to scale his hits as his health goes down. Starts off at 800s and hits 1200s near death.

I think that would be a good addition.
Reply With Quote
  #3  
Old 09-01-2008, 07:10 AM
KLS
Administrator
 
Join Date: Sep 2006
Posts: 1,348
Default

I've actually wanted to implement a ModifyStat(const char *statname, int value) function for a while but just haven't gotten around to it.
Reply With Quote
  #4  
Old 09-02-2008, 07:36 AM
janusd
Sarnak
 
Join Date: Jan 2008
Posts: 47
Default

I'm afraid it's getting more and more necessary. Though we're not at GoD yet, that was the expansion when mobs started scaling in difficulty based on their HP. We COULD just depop and then repop the mob at certain HP% and then when the new one pops spawn it at a certain % all using PERL commands, but given that SPELLS wouldn't carry over, that could potentially and easily wipe a raid. Additionaly, if a script doesn't fire correctly, the original mob would remain which would mean an easier fight, and, depending on how we set up the loot tables, no loot once it goes doen.
Reply With Quote
Reply


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

   

All times are GMT -4. The time now is 11:27 AM.


 

Everquest is a registered trademark of Daybreak Game Company LLC.
EQEmulator is not associated or affiliated in any way with Daybreak Game Company LLC.
Except where otherwise noted, this site is licensed under a Creative Commons License.
       
Powered by vBulletin®, Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Template by Bluepearl Design and vBulletin Templates - Ver3.3