Go Back   EQEmulator Home > EQEmulator Forums > Quests > Quests::Q&A

Quests::Q&A This is the quest support section

Reply
 
Thread Tools Display Modes
  #1  
Old 06-24-2013, 06:39 PM
Bodmingaol
Fire Beetle
 
Join Date: Jun 2010
Location: Oregon, USA
Posts: 26
Default Access loottable_id and manipulate loottable entries via perl?

Is is possible to access an npc's loottable_id and ultimately edit the loottalbe_entries via perl? Would you need to write a MySQL function to do that and call it via perl?

I am exploring the idea of rotating loot drops on a timer.

Thanks
Reply With Quote
  #2  
Old 06-25-2013, 02:57 AM
trevius's Avatar
trevius
Developer
 
Join Date: Aug 2006
Location: USA
Posts: 5,946
Default

You can use DBI to connect to the DB directly from perl and do whatever queries you want. It isn't overly complicated and there are a few posts around the forums that cover it. Here is one that has a bit of info:


http://www.eqemulator.org/forums/showthread.php?t=36355
__________________
Trevazar/Trevius Owner of: Storm Haven
Everquest Emulator FAQ (Frequently Asked Questions) - Read It!
Reply With Quote
  #3  
Old 06-25-2013, 03:50 AM
KLS
Administrator
 
Join Date: Sep 2006
Posts: 1,348
Default

One thing that may throw a wrench in this is loottables are in shared memory which (generally) requires a server reboot to reload.
Reply With Quote
  #4  
Old 06-25-2013, 04:20 AM
Kingly_Krab
Administrator
 
Join Date: May 2013
Location: United States
Posts: 1,589
Default

There is, however, a way to just create the loot within Perl and not have to use the database using quest::addloot.
Code:
sub EVENT_SPAWN
{
	quest::addloot(item_id, charges = 0, equipitem = true or false);
}
Reply With Quote
  #5  
Old 06-25-2013, 05:04 AM
trevius's Avatar
trevius
Developer
 
Join Date: Aug 2006
Location: USA
Posts: 5,946
Default

Quote:
Originally Posted by KLS View Post
One thing that may throw a wrench in this is loottables are in shared memory which (generally) requires a server reboot to reload.
On Storm Haven, we started doing most loot via scripts. You can add loot on the fly anytime via scripts and using the default.pl makes it really easy to split up loot to an entire zone with a high amount of flexibility. For example, I have most of my newer leveling content using a scaling loot system that works via a plugin I made. It scales the number of drops based on how big the group is that first engages the NPC. I think this helps to promote grouping a bit more, because people don't have to worry about needing to kill 6X the amount of whatever mobs to get all of their quest drops for an entire group. So, you can group up to level up quicker without being bottle-necked by waiting for specific drops as much.

We have quite a few plugins created for adding loot in different ways, but here are a couple of examples for adding cash and scaling drops to a group:

Code:
###Usage: plugin::AddRandomCash(MinCash, MaxCash, [ AllowRepeatAdds, NPC ]);
# Adds a random amount of cash to the NPC based on the MinCash and MaxCash settings.
# All rewards are based on Copper amounts (1000 = 1 plat)
# AllowRepeatAdds will allow money to be added to the same NPC more than once if set to 1, or will block extra adds if left off (0)
# NPC is an optional field for setting a specific NPC to add cash to

sub AddRandomCash {

	my $npc = plugin::val('$npc');
	my $MinCash = $_[0];
	my $MaxCash = $_[1];
	my $AllowRepeatAdds = $_[2];
	my $SpecificNPC = $_[3];
	
	if ($SpecificNPC)
	{
		$npc = $SpecificNPC;
	}

	if (!$npc->EntityVariableExists(957) || $AllowRepeatAdds)
	{	
		$npc->SetEntityVariable(957, 1);
		my $CashReward = plugin::RandomRange($MinCash, $MaxCash);

		my $Plat = int($CashReward / 1000);
		my $Gold = int(($CashReward - ($Plat * 1000)) / 100);
		my $Silv = int(($CashReward - (($Plat * 1000) + ($Gold * 100))) / 10);
		my $Copp = $CashReward - (($Plat * 1000) + ($Gold * 100) + ($Silv * 10));
		#plugin::Debug("Random Cash: $CashReward, Plat $Plat, Gold $Gold, Silv $Silv, Copp $Copp");
		
		$npc->AddCash($Copp, $Silv, $Gold, $Plat);
		return $CashReward;
	}
	return 0;
}


###Usage: plugin::ScaleDropToGroup(item_id, Chance[1-100], Scale[1-100]=100, Group_Only=0, Max_Chance=100, Trivial=0);
# Drops an item X amount of times depending on how many clients are in the group.
# Chance is the overall chance for the item to drop
# Scale is the rate that the drop chance decreases to for each additional member (Diminishing return)
# Group_Only is an optional setting.  If 0, solo players will have a chance for this to drop.  If 1, only groups will have a chance for the drop.
# The Group Only option is primarily for adding an item that already exists in the NPC's loot table.
# Max_Chance option allows the chance range to be increased above the default 100.
# Setting Max_Chance to 1000 will make the chance roll from 1 to 1000 instead of 1 to 100 which allows drop rates less than 1%.
# Trivial can enable the trivial loot checks to restrict scaling drops to only groups who can earn experience from the kill (default is disabled 0).
# Example 1: plugin::ScaleDropToGroup(1001, 100, 80);
# This example gives the item a 100% chance to be added, but each additional member reduces that chance to 80% of the previous chance
# Example 2: plugin::ScaleDropToGroup(1001, 5, 80, 1);
# This example gives the item a 5% chance to be added, but each additional member reduces that chance to 80% of the previous chance
# Example 2 will not add the item if an ungrouped client is fighting the NPC.
# Example 3: plugin::ScaleDropToGroup(1001, 1, 100, 0, 500, 1);
# This example will give the item a 0.1% drop chance for each member in the group or for solo players, but only scales to the group if they can get exp from the kill

sub ScaleDropToGroup {
	my $npc = plugin::val('$npc');
	my $client = plugin::val('$client');
	my $userid = plugin::val('$userid');
	my $entity_list = plugin::val('$entity_list');
	my $item_id = $_[0];
	my $drop_chance = $_[1];	# Chance
	my $per_member_chance = $_[2];	# Scale
	my $group_only = $_[3];
	my $max_chance = $_[4];
	my $trivial = $_[5];

	# If the event was triggered by a pet, get the pet's owner as the client
	my $Attacker = $entity_list->GetMobByID($userid);
	my $Owner = 0;
	my $PetOwnerID = 0;
	my $SwarmOwnerID = 0;
	if ($Attacker && $Attacker->IsNPC())
	{
		$PetOwnerID = $Attacker->CastToNPC()->GetOwnerID();
		$SwarmOwnerID = $Attacker->CastToNPC()->GetSwarmOwner();
	}
	if ($PetOwnerID)
	{
		$Owner = $entity_list->GetClientByID($PetOwnerID);
	}
	if ($SwarmOwnerID)
	{
		$Owner = $entity_list->GetClientByID($SwarmOwnerID);
	}
	if ($Owner)
	{
		$client = $Owner;
	}

	if (!$max_chance) { $max_chance = 100; }
	if ($drop_chance > $max_chance) { $drop_chance = $max_chance; }
	if (!$per_member_chance) { $per_member_chance = 100; }

	my $Charge = 0;	
	my $Stack_Size = $npc->GetItemStat($item_id, "stacksize"); 
	my $Has_Charge = $npc->GetItemStat($item_id, "maxcharges"); 
	if ($Stack_Size >= 1) { $Charge = 1; }
	if ($Has_Charge >= 1) { $Charge = $Has_Charge; }

	#plugin::Debug("Starting Plugin");
	# Verify all of the required fields are set properly
	if ($drop_chance > 0 && $item_id)
	{
		if($client)	# Verify we got a client
		{
			my $ClientGroup = $client->GetGroup();	# Check if the client is in a group
			if ($ClientGroup)
			{
				#plugin::Debug("Got Group");
				my $GroupCount = $ClientGroup->GroupCount();	# Count the group members
				my $NPCLevel = $npc->GetLevel();
				my $HighLevel = $ClientGroup->GetHighestLevel();
				my $ExpMaxLevel = (($NPCLevel / 3) * 4);
				#plugin::Debug("Highest Level is $HighLevel - NPC Max Exp Level is $ExpMaxLevel");
				if (!$trivial || $HighLevel <= $ExpMaxLevel)
				{
					if ($GroupCount > 1)
					{
						# Create the variable that tracks the highest number of opponents this NPC has had since it spawned
						if (!$npc->EntityVariableExists($item_id))
						{	
							$npc->SetEntityVariable($item_id, 1);
						}
						my $DropTotal = $npc->GetEntityVariable($item_id);
						#plugin::Debug("Group Total $GroupCount, NPC Total $DropTotal");
						if ($GroupCount > $DropTotal)
						{
							# Save the highest number of opponents
							$npc->SetEntityVariable($item_id, $GroupCount);
							my $scale_rate = 100;
							#plugin::Debug("Scale Rate $scale_rate");
							# Run a loop to do the math and add loot
							my $StartCount = 1;
							if ($group_only)
							{
								$StartCount = 2;
							}
							for ($count = $StartCount; $count <= $GroupCount; $count++)
							{
								#plugin::Debug("Count $count");
								$scale_rate = $scale_rate * $per_member_chance / 100;
								if ($count > $DropTotal)
								{
									#plugin::Debug("Ready to roll to add the loot");
									my $ActualChance = $drop_chance * $scale_rate / $max_chance;
									my $RandomNum = plugin::RandomRange(1, $max_chance);
									#plugin::Debug("Actual Chance $ActualChance - Random Number $RandomNum");
									if ($ActualChance >= $RandomNum)
									{
										#plugin::Debug("Dropping $loottable with an actual chance of $ActualChance and group count of $GroupCount");
										$npc->AddItem($item_id, $Charge, 0);
										#plugin::Debug("Group Drop Added");
									}
								}
							}
						}
					}
				}
			}
			else	# No Group
			{
				if (!$group_only)
				{
					# Create the variable that tracks that this has been rolled for once already
					if (!$npc->EntityVariableExists($item_id))
					{	
						$npc->SetEntityVariable($item_id, 1);
						my $RandomNum = plugin::RandomRange(1, $max_chance);
						if ($drop_chance >= $RandomNum)
						{
							$npc->AddItem($item_id, $Charge, 0);
							#plugin::Debug("Solo Drop Added");
						}
					}
				}
			}
		}
		else	# No Client Attacking
		{
			if (!$group_only)
			{
				# Create the variable that tracks that this has been rolled for once already
				if (!$npc->EntityVariableExists($item_id))
				{	
					$npc->SetEntityVariable($item_id, 1);
					my $RandomNum = plugin::RandomRange(1, $max_chance);
					if ($drop_chance >= $RandomNum)
					{
						$npc->AddItem($item_id, $Charge, 0);
						#plugin::Debug("Solo Drop Added");
					}
				}
			}
		}
	}
}
Side Note: You might notice there are a lot of lines using plugin:ebug(), and that is one that I don't think is available in the standard quests repository. All of the examples above are commented out, so this is not required, but I find it pretty useful. It is nice because you can use it to debug your scripts to see what is happening (or what is failing) and only GMs will see it. This way, if you leave a debug message in place, you don't have to worry about players ever seeing it (vs if you used shout or say for debugs previously).

Code:
#Usage: plugin::Debug("Message", Color, Mob);
# "Message" is a required field and is the message you want to show up in the debug
# Color is an optional field and if not set will default to a pink/purple color
# Mob is an optional field if you want to export a particular mob to debug from (used for getting the name)
# Example 1: plugin::Debug("Event Started", 7);
# Example 2: plugin::Debug("Event Started");

sub Debug {

	my $npc = plugin::val('$npc');
	my $client = plugin::val('$client');
	my $MyMessage = $_[0];
	my $TextColor = $_[1];
	if (!$TextColor)
	{
		$TextColor = 326;	#Set the Text Color for the Message (this one is bright purple)
	}
	my $Mob = $_[2];
	
	if (!$Mob)
	{
		if ($npc)
		{
			# NPC Quest
			$Mob = $npc;
		}
		elsif ($client)
		{
			# Player Quest
			$Mob = $client;
		}
	}
	my $MobName = "NO_NAME";
	if ($Mob) {
		#Get the clean name of the Mob sending the message
		$MobName = $Mob->GetCleanName();	
	}
	
	#Send a message in purple (default) to GMs in the Zone only
	quest::gmsay("$MobName Debugs: $MyMessage", $TextColor);	
}

Here is an example that makes use of those plugins in a default.pl file from one of our zones. Note that it uses EVENT_COMBAT to do the loot, which is so it can determine how to scale the loot drops based on how many members are in the group (if any). This example is fairly complex, but works pretty well in scenarios where you don't need an overly complex loot table for a zone. Using something like this, you could make a custom zone full of loot in no time:
Code:
sub EVENT_COMBAT {

	# Prevent pets or charmed NPCs from loading the default.pl
	if (!$npc || $npc->GetOwnerID() || $npc->GetSwarmOwner())
	{
		return;
	}

	if($combat_state)
	{
		my $NPCRace = $npc->GetRace();
		my $NPCName = $npc->GetName();


		# Array for Loot Drop Selection
		my @LootList = (
			39235,	# Sash of Divine Retribution Armor  ID: 39235
			39218,	# Dragonskull of Relic Armor  ID: 39218
			39221	# Sash of Seven Scales Armor  ID: 39221
		);
		my @ArmorLootList = (
			39220,	# Dragontamer Sleeves Armor  ID: 39220
			46795,	# Windcaller's War Gear Armor  ID: 46795
			46796,	# Windcaller's Under Armor Armor  ID: 46796
			39216,	# Hollowed Dragon Tusk Armor  ID: 39216
			39217,	# Stretched Dragonback Bracer Armor  ID: 39217
			39219,	# Dragonrider Chain Gloves Armor  ID: 39219
			39223,	# Silken Boots of the Magus Armor  ID: 39223
			46797,	# Ringmail Ritual Boots Armor  ID: 46797
			46798	# Plaguehide Boots Armor  ID: 46798
		);

		my $ListLen = @LootList;
		my $ArmorListLen = @ArmorLootList;
		my $SelectLoot = $LootList[plugin::RandomRange(0, $ListLen)];
		my $SelectArmorLoot = $ArmorLootList[plugin::RandomRange(0, $ArmorListLen)];
		#Usage: plugin::ScaleDropToGroup(item_id, chance[1-100], scale[1-100]=100, group_only=0, max_chance=100);
		plugin::ScaleDropToGroup($SelectLoot, 5, 100);
		plugin::ScaleDropToGroup($SelectArmorLoot , 7, 100);
		plugin::ScaleDropToGroup(79980, 20, 20);	# Urn of Rejuvenation - Single Charge clicky (should help soloing)
		
		# Array for Aug Loot Drop Selection
		my @AugLootList = (
			39236,	# Shadowed Stone of Hidden Flame Aug: 8   ID: 39236
			46744,	# Ash of the Fallen Aug: 8   ID: 46744
			39333	# Smoldered Gem of Battle Aug: 8   ID: 39333
		);
		my $AugListLen = @AugLootList;
		my $SelectAugLoot = $AugLootList[plugin::RandomRange(0, $AugListLen)];
		# Adjust Drop Rate
		my $AugDropRate = 3;
		my $AugScaleRate = 100;
		my $SmallExpDropRate = 2;
		my $SmallExpScaleRate = 100;
		my $LargeExpDropRate = 1;
		my $LargeExpScaleRate = 100;
		if ($NPCName =~ /^#/ && $NPCName !~ /^##/)
		{
			#plugin::Debug("Increasing Drop Rates for Augs and Exp Shards (Named)");
			# Named/Rare NPC
			$AugDropRate = 25;
			$AugScaleRate = 20;
			$SmallExpDropRate = 20;
			$SmallExpScaleRate = 20;
			$LargeExpDropRate = 10;
			$LargeExpScaleRate = 20;
		}
		plugin::ScaleDropToGroup($SelectAugLoot, $AugDropRate, $AugScaleRate);
		plugin::ScaleDropToGroup(8479, $SmallExpDropRate, $SmallExpScaleRate);	# 8479 - Small Shard of Experience - Spell: Special Effect - 9999 - ScriptFileID 12345
		plugin::ScaleDropToGroup(8484, $LargeExpDropRate, $LargeExpScaleRate);	# 8484 - Large Shard of Experience - Spell: Special Effect - 9999 - ScriptFileID 12345
		
		# Set Cash Drops
		if ($NPCName =~ /^#/ && $NPCName !~ /^##/)
		{
			# Named/Rare NPC
			plugin::AddRandomCash(6500, 20000);
		}
		else
		{
			# Trash Mobs
			plugin::AddRandomCash(3500, 10000);
		}
		
		# Handle Specific Race or NPC stuff
		
		# Goblins
		if ($NPCRace == $GoblinRace)
		{
			if ($NPCName =~ /sunstone/i)
			{
				plugin::ScaleDropToGroup(8474, 6, 100);	# 8474 - Sunstone Pendant of Loyalty
				plugin::ScaleDropToGroup(8422, 6, 100);	# 8422 - Chewy Goblin Eye
			}		
			if ($NPCName =~ /tidewater/i)
			{
				plugin::ScaleDropToGroup(8478, 6, 100);	# 8478 - Tidewater Pendant of Loyalty
				plugin::ScaleDropToGroup(8422, 6, 100);	# 8422 - Chewy Goblin Eye
				plugin::ScaleDropToGroup(8473, 7, 75);	# 8473 - Tidewater Conch Shell
			}
			plugin::ScaleDropToGroup(8740, 6, 25);	# 8740 - Goblin Bone Shaft - Drops from Goblins in Suncrest Isle
		}

		# All Animals
		if ($NPCRace == $RaptorRace || $NPCRace == $DrakeRace || $NPCRace == $BasiliskRace || $NPCRace == $TigerRace)
		{
			plugin::ScaleDropToGroup(8423, 6, 100);	# 8423 - Tough Animal Heart
		}
	}

}
__________________
Trevazar/Trevius Owner of: Storm Haven
Everquest Emulator FAQ (Frequently Asked Questions) - Read It!

Last edited by trevius; 06-26-2013 at 02:53 AM..
Reply With Quote
  #6  
Old 06-25-2013, 04:01 PM
Bodmingaol
Fire Beetle
 
Join Date: Jun 2010
Location: Oregon, USA
Posts: 26
Default

Wow. Way more than I expected. Thanks all for your posts. I have a lot to look over and think about

Bodmin
Reply With Quote
  #7  
Old 06-25-2013, 04:17 PM
Bodmingaol
Fire Beetle
 
Join Date: Jun 2010
Location: Oregon, USA
Posts: 26
Default

Just so I understand you correctly Trevius, you have the script in a default.pl and it is in each zone directory that you want the script executed? I was reading posts that say you can't put default.pl in a zone directory. I take it that I can?

Bodmin
Reply With Quote
  #8  
Old 06-25-2013, 04:39 PM
Burningsoul
Discordant
 
Join Date: Oct 2009
Posts: 312
Default

Trev, that bottom script opens a WORLD of possibilities for rapid zone itemization. Seriously popped an EQ boner reading that.
Reply With Quote
  #9  
Old 06-25-2013, 07:38 PM
Bodmingaol
Fire Beetle
 
Join Date: Jun 2010
Location: Oregon, USA
Posts: 26
Default

Lol Trevius, I answered my own question about default.pl. Threw a simple one in a zone and wham it worked just fine. Thanks again for the scripts
Reply With Quote
  #10  
Old 06-26-2013, 02:51 AM
trevius's Avatar
trevius
Developer
 
Join Date: Aug 2006
Location: USA
Posts: 5,946
Default

Quote:
Originally Posted by Burningsoul View Post
Trev, that bottom script opens a WORLD of possibilities for rapid zone itemization. Seriously popped an EQ boner reading that.
LOL, yeah, you can setup loot for an entire zone in no time as long as you don't need overly complex loot tables. It could be done from the DB as well, but I find it pretty easy to quickly adjust it when in a script like that. I use it mostly for quest items, since the drop rate scaling to groups is great for quest drops, but it works nicely for other things as well.

I found that once I was able to get a good default.pl template working, I was able to develop custom zones pretty quickly. The section shown above is just a small snippet of my whole default.pl script for that particular zone. It also handles things like attack and death message, random roaming, faction hits, task updates, variation in race/gender and weapons for humanoid races among other things.

Of course, there are benefits to using the DB instead, such as being able to quickly adjust drops server-wide (for things like 2X loot drop rate weekend events and such). If using scripts, you would have to open the default.pl for each zone in order to do that.

Quote:
Originally Posted by Bodmingaol View Post
Lol Trevius, I answered my own question about default.pl. Threw a simple one in a zone and wham it worked just fine. Thanks again for the scripts
Yeah, it used to be that default.pl didn't work in zones, but I added it to work from zones a year or 2 ago and it is very useful when used right.
__________________
Trevazar/Trevius Owner of: Storm Haven
Everquest Emulator FAQ (Frequently Asked Questions) - Read It!
Reply With Quote
  #11  
Old 06-26-2013, 01:32 PM
Akkadius's Avatar
Akkadius
Administrator
 
Join Date: Feb 2009
Location: MN
Posts: 2,071
Default

As far as server wide stuff for NPC's, I recently implemented global_npc.pl so that you can have NPC's globally be governed via Perl. Makes it a lot easier to get things done that way. Also gives you great ideas knowing you can add things globally.

You can do things like

Code:
if($npc->GetCleanName()=~/orc pawn/i && plugin::RandomRange(1, 100) < 5){ quest::addloot(xxx); }

Last edited by Akkadius; 06-26-2013 at 01:41 PM..
Reply With Quote
  #12  
Old 06-26-2013, 03:11 PM
ZionPhoenixGM's Avatar
ZionPhoenixGM
Fire Beetle
 
Join Date: Mar 2013
Posts: 11
Default

Very happy I stumbled across this thread. Will be nice to get these scripts in and get zones flowing.
Reply With Quote
  #13  
Old 06-26-2013, 03:48 PM
Burningsoul
Discordant
 
Join Date: Oct 2009
Posts: 312
Default

Indeed - this, coupled with the global_npc that Akka made, is a tremendous help for zonage. As someone who can't code worth a crap, but is good with existing templates, they're godsends.
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:14 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