PDA

View Full Version : Access loottable_id and manipulate loottable entries via perl?


Bodmingaol
06-24-2013, 06:39 PM
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

trevius
06-25-2013, 02:57 AM
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

KLS
06-25-2013, 03:50 AM
One thing that may throw a wrench in this is loottables are in shared memory which (generally) requires a server reboot to reload.

Kingly_Krab
06-25-2013, 04:20 AM
There is, however, a way to just create the loot within Perl and not have to use the database using quest::addloot.
sub EVENT_SPAWN
{
quest::addloot(item_id, charges = 0, equipitem = true or false);
}

trevius
06-25-2013, 05:04 AM
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:

###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::Debug(), 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).

#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:

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
}
}

}

Bodmingaol
06-25-2013, 04:01 PM
Wow. Way more than I expected. Thanks all for your posts. I have a lot to look over and think about:D

Bodmin

Bodmingaol
06-25-2013, 04:17 PM
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

Burningsoul
06-25-2013, 04:39 PM
Trev, that bottom script opens a WORLD of possibilities for rapid zone itemization. Seriously popped an EQ boner reading that.

Bodmingaol
06-25-2013, 07:38 PM
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

trevius
06-26-2013, 02:51 AM
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.

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.

Akkadius
06-26-2013, 01:32 PM
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


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

ZionPhoenixGM
06-26-2013, 03:11 PM
Very happy I stumbled across this thread. Will be nice to get these scripts in and get zones flowing.

Burningsoul
06-26-2013, 03:48 PM
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.