EQEmulator Forums

EQEmulator Forums (https://www.eqemulator.org/forums/index.php)
-   Quests::Q&A (https://www.eqemulator.org/forums/forumdisplay.php?f=599)
-   -   Access loottable_id and manipulate loottable entries via perl? (https://www.eqemulator.org/forums/showthread.php?t=37002)

Bodmingaol 06-24-2013 06:39 PM

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

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.
Code:

sub EVENT_SPAWN
{
        quest::addloot(item_id, charges = 0, equipitem = true or false);
}


trevius 06-25-2013 05:04 AM

Quote:

Originally Posted by KLS (Post 221979)
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::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).

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

}


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

Quote:

Originally Posted by Burningsoul (Post 221995)
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 (Post 222000)
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

Code:

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.


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

Powered by vBulletin®, Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.