Go Back   EQEmulator Home > EQEmulator Forums > Quests > Quests::Completed

Quests::Completed This is where Completed quests are.

Reply
 
Thread Tools Display Modes
  #1  
Old 07-26-2013, 10:26 AM
jshows1
Sarnak
 
Join Date: Jun 2009
Location: Baton Rouge,LA
Posts: 79
Default Dynamic instancing by level range

This community has been overwhelmingly helpful in assisting me with getting my server up and running. When I first started I had a plethora of questions (will have more I'm sure!) But you guys have been patient and extremely positive.

Now it's time for me to give back to the community. A friend and I have been trying to find a way to create instances that would change level ranges based on the quest requestor's level. Yes this is similar to what LDON's do. With this PL file, however, you canmodify it to make instances of any zone.

In my example below, we've used Crushbone. With this, all you would have to do is populate each version of the instance with the appropriate level of mobs. This, of course, can be any range you want. You determine.

I apologize if this is not as neat as some PL files. We've just recently begun to learn how PL files work and most of the ones we've made have been simply trial nad error to see what works. So it may not be as neat as some, but it works perfectly.

Code:
sub EVENT_SAY {
	my $EnterInst = quest::saylink("Enter the Instance", 2);
	my $Destroy = quest::saylink("destroy", 2);
    my $worse = quest::saylink("worse", 2);
	my $evil = quest::saylink("evil", 2);
	my $InstId = quest::GetInstanceID("crushbone", $version);

	if($text=~/hail/i){
	quest::say("Thank Tunare you're here, $name!  Something dreadful is taking place inside Castle Crushbone.  Yes, yes, I realize that the whole place is dreadful to begin with.  But it's much [$worse] now.");
	}
	if($text=~/worse/i){
	quest::say("Some new evil has moved in and set up shop, destroying everything inside.  Where the Crushbone Orcs were destructive and chaotic, this new [$evil] is much more of a threat to all of Tunare's creatures.");
	}
	if($text=~/evil/i){
	quest::say("if you are willing to assist us, I will need to ensure your safe passage.  The new evil inside Crushbone has not yet reached our dimension.  However, he has infilftrated alternate diminsions.  If we can defeat him in these other planes of existence, perhaps we can prevent him from finding our current diminsion.  Just say when you are ready and I can open a portal enabling you to [$EnterInst].  Also, if you are done fighting, or wish to start over, I can [$Destroy] any old instances that may be lingering. ");
	}
	if ($text =~/Enter the Instance/i) {
		my $j;
		for($j=1; $j<=6; $j++) {
			my $version = $j;
			my $InstId = quest::GetInstanceID("crushbone", $version);
		if ($InstId > 0) {
		quest::MovePCInstance(58, $InstId, 166, -586, 3.13);
		}
		elsif ((quest::GetInstanceID("crushbone", $ulevel + 1)) > 0) {
		quest::MovePCInstance(58, quest::GetInstanceID("crushbone", $ulevel + 1), 166, -586, 3.13);
		}
		elsif ((quest::GetInstanceID("crushbone", $ulevel - 1)) > 0) {
		quest::MovePCInstance(58, quest::GetInstanceID("crushbone", $ulevel - 1), 166, -586, 3.13);
		}
		elsif (($ulevel >= (($version * 10) - 9)) && ($ulevel <= ($version * 10))) {
		my $i_id = quest::CreateInstance("crushbone", $version, 21600);
        quest::AssignGroupToInstance($i_id);
		quest::AssignToInstance($i_id);
		quest::MovePCInstance(58, $i_id, 166, -586, 3.13);
		}
		else {
		}
		}
	}
	if($text=~/destroy/i){
		my $i;
		for($i=0; $i<=6; $i++) {
			quest::DestroyInstance(quest::GetInstanceID("crushbone", $i));
		}
	}
}
We also had to find a way to have the player zone back out of the instance. For this, there may be a better way, but we created an NPC that acts like a zone line. Below is his PL file. Basically we put an NPC where we want the zone line, changed him to an invisible man, made his name ___ so it does not show up, and simply made him untargetable and made it to where players can't aggro or damage him. Again, there may be an easier way, but this works perfectly for us.

Code:
sub EVENT_SPAWN {
	$x = $npc->GetX();
	$y = $npc->GetY();
	quest::set_proximity($x - 70, $x + 70, $y - 70, $y + 70);
}

sub EVENT_ENTER {
	$client->Message(315, "Greater Faydark");
	quest::movepc(54,-56.33,2552.77,18.97,125);
}

sub EVENT_EXIT {
	quest::set_proximity($x - 70, $x + 70, $y - 70, $y + 70);
}
If anyone has any questions, I'd be more than happy to answer them. Again, thank you guys kindly for helping me get started. I hope this can help even a few people.
Reply With Quote
  #2  
Old 07-26-2013, 12:34 PM
rhyotte
Hill Giant
 
Join Date: Jul 2012
Location: Oklahoma
Posts: 222
Default

Nice.... I can see this getting some good use.
Reply With Quote
  #3  
Old 07-26-2013, 04:51 PM
Secrets's Avatar
Secrets
Demi-God
 
Join Date: May 2007
Location: b
Posts: 1,449
Default

A better idea would to be to take the instance ID, have a central zone controller, and scale NPCs in the zone based on level range in a perl script upon NPC spawn with signals and/or entity variables.

You can accomplish this with the modifynpcstat perl command and Akkadius' zone copying tool.
Reply With Quote
  #4  
Old 07-27-2013, 12:29 AM
jshows1
Sarnak
 
Join Date: Jun 2009
Location: Baton Rouge,LA
Posts: 79
Default

Quote:
Originally Posted by Secrets View Post
A better idea would to be to take the instance ID, have a central zone controller, and scale NPCs in the zone based on level range in a perl script upon NPC spawn with signals and/or entity variables.

You can accomplish this with the modifynpcstat perl command and Akkadius' zone copying tool.
This sounds like great information. Thanks a bunch, Secrets. I'll definitely look into this method and learn how to implement what you're talking about. Certainly sounds more efficient than what we did. Again, we're just learning this Pearl stuff, so we're still a little clunky lol.

And thanks a bunch for the encouragement, Rhyotte!
Reply With Quote
  #5  
Old 07-27-2013, 01:18 AM
Akkadius's Avatar
Akkadius
Administrator
 
Join Date: Feb 2009
Location: MN
Posts: 2,067
Default

Quote:
Originally Posted by jshows1 View Post
This sounds like great information. Thanks a bunch, Secrets. I'll definitely look into this method and learn how to implement what you're talking about. Certainly sounds more efficient than what we did. Again, we're just learning this Pearl stuff, so we're still a little clunky lol.

And thanks a bunch for the encouragement, Rhyotte!
Secrets pretty much ripped this idea from what I had implemented and talked about. I've had it on my server for over 8 months, however I haven't had a real content push in a long time YET.

You have a NPC that iterates through the entity list and modify's their stats on the fly based on criteria you specify.

In my case I do really crafty and flexible loading from a database, and this NPC exists in every zone and the NPC's are scaled based on two keys:
  • Level
  • Type (Trash, Named, Raid)

Here is my live and working zone controller if you want to try and spin an idea of how I do this.

Code:
### Akkadius
### Zonecontroller
### This Entity sits in every zone ready to accept signals to handle various requests
### First use of this will be to handle automatic scaling of the zone if desired

### Insert Zone Controller into database
### SQL: (Assumes NPC ID 50)
### INSERT INTO `npc_types` (`id`, `name`, `lastname`, `level`, `race`, `class`, `bodytype`, `hp`, `mana`, `gender`, `texture`, `helmtexture`, `size`, `hp_regen_rate`, `mana_regen_rate`, `loottable_id`, `merchant_id`, `alt_currency_id`, `npc_spells_id`, `npc_faction_id`, `adventure_template_id`, `trap_template`, `mindmg`, `maxdmg`, `attack_count`, `npcspecialattks`, `aggroradius`, `face`, `luclin_hairstyle`, `luclin_haircolor`, `luclin_eyecolor`, `luclin_eyecolor2`, `luclin_beardcolor`, `luclin_beard`, `drakkin_heritage`, `drakkin_tattoo`, `drakkin_details`, `armortint_id`, `armortint_red`, `armortint_green`, `armortint_blue`, `d_meele_texture1`, `d_meele_texture2`, `prim_melee_type`, `sec_melee_type`, `runspeed`, `MR`, `CR`, `DR`, `FR`, `PR`, `Corrup`, `see_invis`, `see_invis_undead`, `qglobal`, `AC`, `npc_aggro`, `spawn_limit`, `attack_speed`, `findable`, `STR`, `STA`, `DEX`, `AGI`, `_INT`, `WIS`, `CHA`, `see_hide`, `see_improved_hide`, `trackable`, `isbot`, `exclude`, `ATK`, `Accuracy`, `slow_mitigation`, `version`, `maxlevel`, `scalerate`, `private_corpse`, `unique_spawn_by_name`, `underwater`, `isquest`, `emoteid`) VALUES (50, 'zonecontroller', NULL, 1, 240, 1, 11, 31, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 'ZiGH', 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 28, 1.25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 75, 75, 75, 75, 80, 75, 75, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0);

sub EVENT_SPAWN{
	$npc->TempName("");
	#$Debug = " ";
	quest::gmsay("[Zonecontroller] [ALIVE] Zone: $zonesn Instance Version: $instanceversion InstID $instanceversion", 15);
	
	#LoadPetTable(); ### Load Pet Table into Memory for referencing
	#LoadPetScaleData(); ### Load Pet Scaling Table into Memory
	
	LoadStaticNPCScaling($zonesn); ### Load NPC Static Scaling
	LoadStaticZoneScaling(); ### Load Zone Static Scaling
	
	quest::settimer("scaleall", 3); ### Run Scaling routine
	$ScalingLoaded = "";
	
	# %DreamZones = plugin::LoadDreamZoneList();
	# $n = 1;
	# while($DreamZones{$n}[0]){
	# 	if($instanceversion == 1 && $DreamZones{$n}[0] eq $zonesn){ 
	# 		if(!$entity_list->GetNPCByNPCTypeID(1006398)){
	# 			$query = "SELECT zone, x, y, z, id FROM `zone_points` WHERE `zone` = '". $zonesn ." AND `version` = " . $instanceversion . "";
	# 			$connect = plugin::LoadMysql();
	# 			quest::shout("loading zone points for dream zones");
	# 			$query_handle = $connect->prepare($query); $query_handle->execute();
	# 			while(@row = $query_handle->fetchrow_array()){
	# 				quest::spawn2(1006398, 0, 0, int($row[1]), int($row[2]), int($row[3]), 0);
	# 			}
	# 		}
	# 	}
	# 	$n++;
	# }
}

sub EVENT_TIMER{
	if($timer eq "scaleall"){ ScaleProcedure(); quest::stoptimer("scaleall"); quest::gmsay("Scaling routine complete", 15);  }
	if($timer eq "levelset"){
		@ent = $entity_list->GetNPCList();
		$Level = $npc->GetEntityVariable("queuesetlevel2");
		$Variance = $npc->GetEntityVariable("queuesetlevel3");
		foreach $NPC (@ent){
			if($NPC->GetNPCTypeID() > 1000){
				if($Variance > 0){ } else{ $Variance = 0; }
				$NPC->SetLevel($Level + (RandomRange(0 - $Variance, $Variance)));
			}
		}
		$npc->SetEntityVariable("queuesetlevel", 0);
		$npc->SetEntityVariable("queuesetlevel2", 0);
		$npc->SetEntityVariable("queuesetlevel3", 0);
		quest::gmsay("Level Scaling Done...", 15);
		quest::stoptimer("levelset");
	}
}

sub EVENT_SIGNAL{
	if($signal == 10){ ScaleProcedure(); }
	if($signal == 11){ $npc->SetEntityVariable("queuesetlevel", 1); return; }
	if($npc->GetEntityVariable("queuesetlevel") > 0 && $npc->GetEntityVariable("queuesetlevel2") == 0){ $npc->SetEntityVariable("queuesetlevel2", $signal); quest::settimer("levelset", 1); }
	if($npc->GetEntityVariable("queuesetlevel2") > 0){ $npc->SetEntityVariable("queuesetlevel3", $signal); }
	### Getting Signal from Spawned NPC ###
	if($signal == 21){ $npc->SetEntityVariable("queuesetnpc", 1);  return; }
	if($npc->GetEntityVariable("queuesetnpc") eq "1" && $ScalingLoaded == 3){ $npc->SetEntityVariable("queuesetnpc", 0); ScaleProcedure($signal); }
	if($signal == 23){ $Debug = 1; }
}

sub ScaleProcedure{
	### Read from DB
	if(!$SD[1][0][0]){ LoadScaling(); } ### Scaling Vars Empty, reload them from DB
	$NPC = 0; $pop = "";
	
	if($_[0]){ 
		@ent = $entity_list->GetNPCByNPCTypeID($_[0]); 
		$pop = " POP"; 
		# quest::gmsay("Scaling NPC ID " . $_[0], 15);
	}
	else{ @ent = $entity_list->GetNPCList(); $ScalingLoaded = 2; }
	foreach $NPC (@ent){
		### Pet Scaling Handler
		my $IsPet = $PetD[$NPC->GetNPCTypeID()][0]; if(!$IsPet){ $IsPet = 0; }
		if(($NPC->GetOwnerID() != 0 || $IsPet > 0) && ($entity_list->GetClientByName("AkkaDark") || $entity_list->GetClientByName("Akkamage") || $entity_list->GetClientByName("Akkasham") || $entity_list->GetClientByName("Akkabeast"))){ 
			if($PetD[$NPC->GetNPCTypeID()][0] > 0 && $PetD[$NPC->GetNPCTypeID()][3] == 1){
				###Swarm Pet
				#quest::shout("A Swarm Pet has been casted... Processing code...");
				my $PetLevel = $NPC->GetLevel();
				$NPC->ModifyNPCStat("max_hp", $PetSD[$PetLevel][1]); 
				$NPC->SetHP($NPC->GetMaxHP());
				$NPC->ModifyNPCStat("max_mana", $PetSD[$PetLevel][2]);
				$NPC->ModifyNPCStat("min_hit", $PetSD[$PetLevel][3]);
				$NPC->ModifyNPCStat("max_hit", $PetSD[$PetLevel][4]);
			}else{
				my $PetOwnerID = $entity_list->GetClientByID($NPC->GetOwnerID());
				if($PetOwnerID){ $PetOwner = $PetOwnerID->GetCleanName(); }
				my $PetOwnerEnt = $entity_list->GetClientByID($NPC->GetOwnerID());
				my $PetName = $NPC->GetCleanName();
				my $PetLevel = $PetOwnerEnt->GetEntityVariable("originlevel");
				#my $PetEntID = $PetOwnerEnt->SetEntityVariable($NPC);
				$NPC->SetLevel($PetOwnerEnt->GetLevel());
				my $PetPower = $PetOwnerEnt->GetEntityVariable("petpower") / 100 + 1; if($PetPower > 0){}else{ $PetPower = 1; }
				if($Debug){ quest::gmsay("[Zonecontroller]: Found pet to scale - Owner " . $PetOwner . " Pet: " . $PetName, 15); }
				$NPC->ModifyNPCStat("max_hp", $PetSD[$PetLevel][1] + ($PetOwnerEnt->GetINT() * 10) * $PetPower);
				if($Debug){ quest::gmsay("[Zonecontroller]: Pet: " . $PetName . " max_hp: " . $PetSD[$PetLevel][1] . " INT mod " . ($PetOwnerEnt->GetINT() * 10) . " Pet Power: " . $PetPower ." Final " . ($PetSD[$PetLevel][1] + ($PetOwnerEnt->GetINT() * 10) * $PetPower), 15); }
				$NPC->SetHP($NPC->GetMaxHP());
				$NPC->ModifyNPCStat("max_mana", $PetSD[$PetLevel][2]);
				$NPC->ModifyNPCStat("min_hit", $PetSD[$PetLevel][3]);
				$NPC->ModifyNPCStat("max_hit", $PetSD[$PetLevel][4] + ($PetOwnerEnt->GetCHA() * 5) * ($PetPower / 2));
				if($Debug){ quest::gmsay("[Zonecontroller]: Pet: " . $PetName . " max_hit: " . $PetSD[$PetLevel][4] . " CHA mod " . ($PetOwnerEnt->GetCHA() * 5) . " Pet Power: " . ($PetPower / 2) ." Final " . ($PetSD[$PetLevel][4] + ($PetOwnerEnt->GetCHA() * 5) * ($PetPower / 2)), 15); }
				$NPC->ModifyNPCStat("attack_speed", $PetSD[$PetLevel][5]);
				$NPC->ModifyNPCStat("ac",  $PetSD[$PetLevel][6]);
				$NPC->ModifyNPCStat("hp_regen", $PetSD[$PetLevel][7]);
				$NPC->ModifyNPCStat("spellscale", $PetSD[$PetLevel][8]);
				$NPC->ModifyNPCStat("healscale", $PetSD[$PetLevel][9]);
				$NPC->ModifyNPCStat("special_attacks", $PetSD[$PetLevel][10]);
				$NPC->ModifyNPCStat("accuracy", 50);
				#quest::shout($PetPower);
			}
		}
	
		### Hide Invis NPC Names 
		if(($NPC->GetRace() == 127 || $NPC->GetRace() == 240) && $NPC->GetLevel() < 100 && $NPC->GetNPCTypeID() > 1000){ if($NPC->GetCleanName()=~/shadowed/i){} else{ $NPC->TempName(""); $NPC->ModifyNPCStat("special_attacks", "AZ"); } }
		
		if($NPC->GetEntityVariable("Scaled") != 1){
			#Static NPC Scaling
			$n_name = $NPC->GetCleanName(); $n_name =~ tr/ /_/;
			if($N_SD{$n_name}[0]){  ScaleNPCStatic($NPC, $n_name);  }
		}
		### SCALING
		#$NPC->Say("Scaling...");
		if($NPC->GetPetSpellID() == 0 && $NPC->GetRace() != 127 && $NPC->GetRace() != 240 && $NPC->GetClass() < 20 && $NPC->GetNPCTypeID() > 1000 && $NPC->GetEntityVariable("Scaled") != 1){
			
			$NTYPE = 0; $NN = "NPC";
			if(substr($NPC->GetName(), 0, 1) eq "#" && substr($NPC->GetName(), 1, 2) ne "#"){ $NTYPE = 1; $NN = "Named"; } 
			if(substr($NPC->GetName(), 0, 2) eq "##" && substr($NPC->GetName(), 2, 3) ne "#"){ $NTYPE = 2; $NN = "Raid"; }
			
			$NPC->ModifyNPCStat("max_hp", $SD[$NPC->GetLevel()][$NTYPE][2] * $SZD{$zonesn}[$instanceversion][$NTYPE][2]);
			$NPC->ModifyNPCStat("max_mana", $SD[$NPC->GetLevel()][$NTYPE][3]  * $SZD{$zonesn}[$instanceversion][$NTYPE][3]);
			$NPC->ModifyNPCStat("min_hit", $SD[$NPC->GetLevel()][$NTYPE][4] *  $SZD{$zonesn}[$instanceversion][$NTYPE][4]);
			$NPC->ModifyNPCStat("max_hit", $SD[$NPC->GetLevel()][$NTYPE][5] *  $SZD{$zonesn}[$instanceversion][$NTYPE][5]);
			$NPC->ModifyNPCStat("attack_speed", $SD[$NPC->GetLevel()][$NTYPE][6] *  $SZD{$zonesn}[$instanceversion][$NTYPE][6]);
			$NPC->ModifyNPCStat("ac", $SD[$NPC->GetLevel()][$NTYPE][7] *  $SZD{$zonesn}[$instanceversion][$NTYPE][7]);
			$NPC->ModifyNPCStat("str", $SD[$NPC->GetLevel()][$NTYPE][8]);
			$NPC->ModifyNPCStat("sta", $SD[$NPC->GetLevel()][$NTYPE][9]);
			$NPC->ModifyNPCStat("dex", $SD[$NPC->GetLevel()][$NTYPE][10]);
			$NPC->ModifyNPCStat("agi", $SD[$NPC->GetLevel()][$NTYPE][11]);
			$NPC->ModifyNPCStat("int", $SD[$NPC->GetLevel()][$NTYPE][12]);
			$NPC->ModifyNPCStat("wis", $SD[$NPC->GetLevel()][$NTYPE][13]);
			$NPC->ModifyNPCStat("cha", $SD[$NPC->GetLevel()][$NTYPE][14]);
			$NPC->ModifyNPCStat("mr", $SD[$NPC->GetLevel()][$NTYPE][15]);
			$NPC->ModifyNPCStat("cr", $SD[$NPC->GetLevel()][$NTYPE][16]);
			$NPC->ModifyNPCStat("dr", $SD[$NPC->GetLevel()][$NTYPE][17]);
			$NPC->ModifyNPCStat("fr", $SD[$NPC->GetLevel()][$NTYPE][18]);
			$NPC->ModifyNPCStat("pr", $SD[$NPC->GetLevel()][$NTYPE][19]);
			
			
			### Is there a static zone entry?
			if($SZD{$zonesn}[$instanceversion][$NTYPE][11] != 1){
				$NPC->ModifyNPCStat("special_attacks", $SZD{$zonesn}[$instanceversion][$NTYPE][11]);
			}else{
				$NPC->ModifyNPCStat("special_attacks", $SD[$NPC->GetLevel()][$NTYPE][21]);
			}
			$NPC->ModifyNPCStat("hp_regen", $SD[$NPC->GetLevel()][$NTYPE][20]  *  $SZD{$zonesn}[$instanceversion][$NTYPE][10]);
			$NPC->SetEntityVariable("hpregen", $SD[$NPC->GetLevel()][$NTYPE][20]  *  $SZD{$zonesn}[$instanceversion][$NTYPE][10]);
			$NPC->ModifyNPCStat("spellscale", $SD[$NPC->GetLevel()][$NTYPE][22]  *  $SZD{$zonesn}[$instanceversion][$NTYPE][9]);
			$NPC->ModifyNPCStat("healscale", $SD[$NPC->GetLevel()][$NTYPE][23]  *  $SZD{$zonesn}[$instanceversion][$NTYPE][8]);
			
			$LID = (200000 + ($NTYPE * 1000) + $NPC->GetLevel());
			if($NPC->GetLoottableID() != $LID){
				$NPC->ModifyNPCStat("loottable_id", (210000 + ($NTYPE * 1000) + $NPC->GetLevel())); $NPC->AddLootTable();
				$NPC->ModifyNPCStat("loottable_id", (200000 + ($NTYPE * 1000) + $NPC->GetLevel())); $NPC->AddLootTable();  
			}
			$NPC->SetHP($NPC->GetMaxHP());
			$NPC->SetEntityVariable("Scaled", 1); if($Debug){ $NPC->SetEntityVariable("ScaledType", 1); }
			
			if($Debug){
				quest::gmsay("Scaling $pop $NN [" . $NPC->GetCleanName() . "] 
					HP:[". $SD[$NPC->GetLevel()][$NTYPE][2] * $SZD{$zonesn}[$instanceversion][$NTYPE][2] . "]
					hp_regen:[". ($SD[$NPC->GetLevel()][$NTYPE][20]  *  $SZD{$zonesn}[$instanceversion][$NTYPE][10]) . "]
					min_hit:[". ($SD[$NPC->GetLevel()][$NTYPE][4] *  $SZD{$zonesn}[$instanceversion][$NTYPE][4]) . "]
					max_hit:[".( $SD[$NPC->GetLevel()][$NTYPE][5] *  $SZD{$zonesn}[$instanceversion][$NTYPE][5]) . "]
					spec:[". $SD[$NPC->GetLevel()][$NTYPE][21] . "] 
					NTYPE:[". $NTYPE . "] 
					", 15);
			}
			
			if(!$SD[$NPC->GetLevel()][$NTYPE][0]){ quest::gmsay($NPC->GetCleanName() . ": ERROR! Missing Scaling Entry! For level " . $NPC->GetLevel() . " Type: $NTYPE", 13); }
		}
		# Level 255 NPC's are considered quest NPC's and shall not be attacked #
		if($NPC->GetLevel() == 255){ $NPC->ModifyNPCStat("special_attacks", "AHGZ"); } 
	}
	if($ScalingLoaded == 2){ $ScalingLoaded = 3; }
}

sub LoadScaling{
	$connect = plugin::LoadMysql();
	$query = "SELECT
		cust_npc_scaling.level,
		cust_npc_scaling.type,
		cust_npc_scaling.hp,
		cust_npc_scaling.mana,
		cust_npc_scaling.mindmg,
		cust_npc_scaling.maxdmg,
		cust_npc_scaling.attack_speed,
		cust_npc_scaling.AC,
		cust_npc_scaling.STR,
		cust_npc_scaling.STA,
		cust_npc_scaling.DEX,
		cust_npc_scaling.AGI,
		cust_npc_scaling._INT,
		cust_npc_scaling.WIS,
		cust_npc_scaling.CHA,
		cust_npc_scaling.MR,
		cust_npc_scaling.CR,
		cust_npc_scaling.DR,
		cust_npc_scaling.FR,
		cust_npc_scaling.PR,
		cust_npc_scaling.hp_regen,
		cust_npc_scaling.npcspecialattks,
		cust_npc_scaling.spellscale,
		cust_npc_scaling.healscale
		FROM
		cust_npc_scaling
		ORDER BY cust_npc_scaling.level, cust_npc_scaling.type";
	$query_handle = $connect->prepare($query);
	$query_handle->execute();
	$NTYPE = 0;
	while (@row = $query_handle->fetchrow_array()){ $SD[$row[0]][$row[1]] = [@row]; }
}

sub LoadStaticZoneScaling{
	### Load Static Zone Scaling Data ### 
	$connect = plugin::LoadMysql();
	### Default empty values to 1
	for($i = 2; $i < 17; $i++){ 
		for($t = 0; $t < 3; $t++){
			if(!$SZD{$zonesn}[$instanceversion][$t][$i] || $SZD{$zonesn}[$instanceversion][$t][$i] == 0){ 
				$SZD{$zonesn}[$instanceversion][$t][$i] = 1; 
			}
		}
	} 
	
	$query = "SELECT `zonesn`, `version`, `hp`, `mana`, `mindmg`, `maxdmg`, `attack_speed`, `ac`, `healscale`, `spellscale`, `hpregen`, `specialattacks`, `type` FROM `cust_npc_zonescale_static` WHERE `zonesn` = '" . $zonesn . "'"; $query_handle = $connect->prepare($query); $query_handle->execute(); my @SZD;
	while (@row = $query_handle->fetchrow_array()){ 
		$SZD{$row[0]}[$row[1]][$row[12]] = [@row];  
		if($SZD{$zonesn}[$instanceversion][0]){
			$Mod = $SZD{$zonesn}[$instanceversion][2]; $npc->SetEntityVariable("ScaleMod", $Mod);
			quest::gmsay("[Zonecontroller] (Static Data) [Type] : " . $row[12] . " Zone Data: HP=" . ($SZD{$zonesn}[$instanceversion][$row[12]][2] * 100) . "(%%) Mana=" . ($SZD{$zonesn}[$instanceversion][$row[12]][3] * 100) . "(%%) MINDMG=" . ($SZD{$zonesn}[$instanceversion][$row[12]][4] * 100) . "(%%) MAXDMG=" . ($SZD{$zonesn}[$instanceversion][$row[12]][5] * 100) . "(%%) ATK_SPD=" . ($SZD{$zonesn}[$instanceversion][$row[12]][6] * 100) . "(%%) AC=" . ($SZD{$zonesn}[$instanceversion][$row[12]][7] * 100) . "(%%) SPELLSCALE=" . ($SZD{$zonesn}[$instanceversion][$row[12]][8] * 100) . "(%%) HEALSCALE=" . ($SZD{$zonesn}[$instanceversion][$row[12]][9] * 100) . "(%%) HPREGEN=" . ($SZD{$zonesn}[$instanceversion][$row[12]][10] * 100) . "(%%)", 15);
			
		}
	}
}

sub LoadStaticNPCScaling{
	$connect = plugin::LoadMysql();
	$query = "SELECT
		cust_npc_scale_static.`name`,
		cust_npc_scale_static.zonesn,
		cust_npc_scale_static.hp,
		cust_npc_scale_static.mana,
		cust_npc_scale_static.mindmg,
		cust_npc_scale_static.maxdmg,
		cust_npc_scale_static.attack_speed,
		cust_npc_scale_static.AC,
		cust_npc_scale_static.STR,
		cust_npc_scale_static.STA,
		cust_npc_scale_static.DEX,
		cust_npc_scale_static.AGI,
		cust_npc_scale_static._INT,
		cust_npc_scale_static.WIS,
		cust_npc_scale_static.CHA,
		cust_npc_scale_static.MR,
		cust_npc_scale_static.CR,
		cust_npc_scale_static.DR,
		cust_npc_scale_static.FR,
		cust_npc_scale_static.PR,
		cust_npc_scale_static.hp_regen,
		cust_npc_scale_static.spellscale,
		cust_npc_scale_static.healscale,
		cust_npc_scale_static.npcspecialattks,
		cust_npc_scale_static.usediabloloot,
		cust_npc_scale_static.localloottable
		FROM
		cust_npc_scale_static
		WHERE cust_npc_scale_static.zonesn = '" . $_[0] . "' OR cust_npc_scale_static.zonesn = 'global'";
	$query_handle = $connect->prepare($query);
	$query_handle->execute();
	$NTYPE = 0;
	while (@row = $query_handle->fetchrow_array()){ $N_SD{$row[0]} = [@row]; quest::gmsay("[Zonecontroller]: Loading static NPC data for NPC: '" . $row[0] ."'", 15); }
}

sub LoadPetTable{
	$connect = plugin::LoadMysql();
	$query = "SELECT
		pets.npcID,
		pets.type,
		pets.petpower,
		pets.temp,
		pets.petcontrol,
		pets.petnaming,
		pets.monsterflag,
		pets.equipmentset
		FROM
		pets
		";
	$query_handle = $connect->prepare($query);
	$query_handle->execute();
	if($Debug){ quest::gmsay("[Zonecontroller]: Loading pet table...", 15); }
	while (@row = $query_handle->fetchrow_array()){ $PetD[$row[0]] = [@row]; if($Debug){ quest::gmsay("[Zonecontroller]: Loading Pet: '" . $row[0] . " - " . $row[1] . "'", 15); } }
	if($Debug){ quest::gmsay("[Zonecontroller]: Loading pet table complete", 15); }
}

sub LoadPetScaleData{
	$connect = plugin::LoadMysql();
	$query = "SELECT
		cust_pet_scaling_entries.level,
		cust_pet_scaling_entries.hp,
		cust_pet_scaling_entries.mana,
		cust_pet_scaling_entries.mindmg,
		cust_pet_scaling_entries.maxdmg,
		cust_pet_scaling_entries.attack_speed,
		cust_pet_scaling_entries.AC,
		cust_pet_scaling_entries.hp_regen,
		cust_pet_scaling_entries.spellscale,
		cust_pet_scaling_entries.healscale,
		cust_pet_scaling_entries.npcspecialattks
		FROM
		cust_pet_scaling_entries
		order by level
		";
	$query_handle = $connect->prepare($query);
	$query_handle->execute();
	while (@row = $query_handle->fetchrow_array()){ $PetSD[$row[0]] = [@row]; }
	if($Debug){ quest::gmsay("[Zonecontroller]: Loading pet scaling complete", 15); }
}

sub ScaleNPCStatic{
	my $SS = $_[0];
	quest::shout("Found data for NPC " . $SS->GetCleanName() . ' - ' . $_[1]);
	$NTYPE = 0; $NN = "NPC";
	if(substr($SS->GetName(), 0, 1) eq "#" && substr($SS->GetName(), 1, 2) ne "#"){ $NTYPE = 1; $NN = "Named"; }
	if(substr($SS->GetName(), 0, 2) eq "##" && substr($SS->GetName(), 2, 3) ne "#"){ $NTYPE = 2; $NN = "Raid"; }

	$SS->ModifyNPCStat("max_hp", $N_SD{$_[1]}[2]);
	$SS->ModifyNPCStat("max_mana", $N_SD{$_[1]}[3]);
	$SS->ModifyNPCStat("min_hit", $N_SD{$_[1]}[4]);
	$SS->ModifyNPCStat("max_hit", $N_SD{$_[1]}[5]);
	$SS->ModifyNPCStat("attack_speed", $N_SD{$_[1]}[6]);
	$SS->ModifyNPCStat("ac", $N_SD{$_[1]}[7]);
	$SS->ModifyNPCStat("str", $N_SD{$_[1]}[8]);
	$SS->ModifyNPCStat("sta", $N_SD{$_[1]}[9]);
	$SS->ModifyNPCStat("dex", $N_SD{$_[1]}[10]);
	$SS->ModifyNPCStat("agi", $N_SD{$_[1]}[11]);
	$SS->ModifyNPCStat("int", $N_SD{$_[1]}[12]);
	$SS->ModifyNPCStat("wis", $N_SD{$_[1]}[13]);
	$SS->ModifyNPCStat("cha", $N_SD{$_[1]}[14]);
	$SS->ModifyNPCStat("mr", $N_SD{$_[1]}[15]);
	$SS->ModifyNPCStat("cr", $N_SD{$_[1]}[16]);
	$SS->ModifyNPCStat("dr", $N_SD{$_[1]}[17]);
	$SS->ModifyNPCStat("fr", $N_SD{$_[1]}[18]);
	$SS->ModifyNPCStat("pr", $N_SD{$_[1]}[19]);
	$SS->ModifyNPCStat("hp_regen", $N_SD{$_[1]}[20]);
	$SS->ModifyNPCStat("spellscale", $N_SD{$_[1]}[21]);
	$SS->ModifyNPCStat("healscale", $N_SD{$_[1]}[22]);
	$SS->ModifyNPCStat("special_attacks", $N_SD{$_[1]}[23]);
	
	if($N_SD{$_[1]}[24] == 1){ ### Tells Static Entry to use Diablo Loot
		$LID = (200000 + ($NTYPE * 1000) + $SS->GetLevel());
		if($SS->GetLoottableID() != $LID){
			$SS->ModifyNPCStat("loottable_id", (210000 + ($NTYPE * 1000) + $SS->GetLevel())); $SS->AddLootTable();
			$SS->ModifyNPCStat("loottable_id", (200000 + ($NTYPE * 1000) + $SS->GetLevel())); $SS->AddLootTable();  
		}
	}
	
	if($N_SD{$_[1]}[25] > 0){ ### Add an additional Loot table
		$SS->ModifyNPCStat("loottable_id", $N_SD{$_[1]}[25]); $SS->AddLootTable();  
	}
	
	$SS->SetEntityVariable("Scaled", 1); if($Debug){ $SS->SetEntityVariable("ScaledType", 2); }
}

sub RandomRange {

	my $MinRandom = $_[0];
	my $MaxRandom = $_[1];

	my $RandomResult = int(rand(($MaxRandom + 1) - $MinRandom)) + $MinRandom;
	if ($RandomResult > $MaxRandom)
	{
		return $MaxRandom;
	}
	return $RandomResult;

}
Reply With Quote
  #6  
Old 07-27-2013, 11:14 AM
jshows1
Sarnak
 
Join Date: Jun 2009
Location: Baton Rouge,LA
Posts: 79
Default

Wow Thanks Akkadius. This sounds like a powerful tool. Real quick, how do you actually implement this? Do I simply create a mob in the zone I want and then put his pl file in that zone folder to get it to work?
Reply With Quote
  #7  
Old 07-27-2013, 01:39 PM
Akkadius's Avatar
Akkadius
Administrator
 
Join Date: Feb 2009
Location: MN
Posts: 2,067
Smile

Quote:
Originally Posted by jshows1 View Post
Wow Thanks Akkadius. This sounds like a powerful tool. Real quick, how do you actually implement this? Do I simply create a mob in the zone I want and then put his pl file in that zone folder to get it to work?
Well it's not a simple implementation, but as you can see in my notes, it is a NPC id of 50, it can be whatever you want it to be.

So how I achieve this automagically, is I insert it into the enterzone of the global_player.pl, if the NPC isn't in the zone when a player zones in, pop him. When he pops, he will go through the scaling routine.

If he isn't already a 'spawn' of the zone, I add him to a spawn regardless of instance version.

Code:
sub EVENT_ENTERZONE {
	if(!$entity_list->GetNPCByNPCTypeID(50)){ 
		quest::spawn2(50, 0, 0, 0, 0, 0, 0); 
		$client->NPCSpawn($entity_list->GetNPCByNPCTypeID(50), "add", 1); 
	} ### Automatic Scaling
}
Another question you might have. Yes, that's fine and dandy, but what about when a NPC spawns and needs to be scaled again?

Well, that's the main reason for me implementing global_npc.pl amongst other reasons.

Here is what I do, I send a signal to the zone controller, first with a unique signal telling the controller that 'HEY! I need to be scaled BEOTCH!' so the zone controller goes, ok yes I got it.

Once again, first he sends signal 21, then he sends the NPC Type ID. So the controller performs an iteration and only scales NPC's with that type ID.

Code:
sub EVENT_SPAWN {
	if($npc->GetEntityVariable("Scaled") != 1){ ### If not flagged as scaled, then scale the NPC
		quest::signalwith(50, 21, 0);
		quest::signalwith(50, $npc->GetNPCTypeID(), 0);
	}
}
The NPC recieves signal 21, sets a entity variable essentially 'flagging' itself for a split second like a catcher in a baseball game that he knows he is about to catch a NPCTypeID as a signal. As soon as he catches the NPCTypeID he releases the entity variable and gets ready for any other scaling requests.

So what did I achieve in the end with all of this?

TONS and TONS of time SAVED by having to scale my zones, I create generic 'ranges' that NPC's SHOULD be at based on their level AND type (Trash, named, raid). If I wanted to up the difficulty I had it set per zone with multipliers as you might be able to rip apart in my code.

All of the table pulls are custom, all of the framework needed to build a system like this is already there after implementing all of the source backend things. Through this thread I am revealing this for the first time.

Once I get my content release done, I will probably end up releasing a good chunk of my work including this one, but I've been so busy in the past years that is all variable.

If you have any questions regarding this type of implementation let me know.
Reply With Quote
  #8  
Old 07-27-2013, 02:13 PM
jshows1
Sarnak
 
Join Date: Jun 2009
Location: Baton Rouge,LA
Posts: 79
Default

Akkadius, I for one am very grateful for you sharing this information. I will certainly be looking to use this. I'm sure I speak for the rest of the community when I offer my thanks. I'm sure this will make a lot of people happy to use this code and am looking forward to seeing what you end up implementing in the future.
Reply With Quote
  #9  
Old 03-06-2023, 11:31 AM
Fridgecritter
Hill Giant
 
Join Date: Feb 2008
Posts: 188
Default

Just a quick note that dimension is spelled correctly the first time, but the two times after that, it is spelled incorrectly.

##EDIT -- I just noticed the date on this post. Sorry for the necro.
Reply With Quote
Reply

Thread Tools
Display Modes

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 06:59 PM.


 

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 - 2023, Jelsoft Enterprises Ltd.
Template by Bluepearl Design and vBulletin Templates - Ver3.3