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 02-15-2012, 03:05 PM
c0ncrete's Avatar
c0ncrete
Dragon
 
Join Date: Dec 2009
Posts: 719
Default $corpse->RemoveItem($slotID) issue

Any suggestions as to why RemoveItem() might not work on an NPC corpse? I'm trying to delete the last item the NPC was manually given after spawning and having it killed using a line such as this one:

$corpse->RemoveItem($corpse->CountItems());

I'm currently using $corpse->Delete() to keep the item in question from being retrieved, but I'd like to be able to keep the original loot generated upon spawning.

NOTE:
I've looked in the source and nothing is really standing out to indicate I'm way off base in my understanding of how this function should work.
Reply With Quote
  #2  
Old 02-15-2012, 08:32 PM
pfyon's Avatar
pfyon
Discordant
 
Join Date: Mar 2009
Location: Ottawa
Posts: 495
Default

I haven't looked at the documentation for the perl function or the source, but are items enumerated from 0? If so, you might need something like

Code:
$corpse->RemoveItem($corpse->CountItems() - 1);
to remove the last item (since item 1 is at index 0, last item would be at index -1).
Reply With Quote
  #3  
Old 02-15-2012, 08:48 PM
c0ncrete's Avatar
c0ncrete
Dragon
 
Join Date: Dec 2009
Posts: 719
Default

yeah, sorry. i guess i forgot to mention that i've tried that as well before posting my question.
Reply With Quote
  #4  
Old 02-16-2012, 01:12 AM
Noport
Opcode Ninja
 
Join Date: Mar 2009
Location: San francisco
Posts: 426
Default

just for grins try this opcode OP_LootComplete=0x528f see if that helps
Reply With Quote
  #5  
Old 02-16-2012, 01:37 AM
c0ncrete's Avatar
c0ncrete
Dragon
 
Join Date: Dec 2009
Posts: 719
Default

Quote:
Originally Posted by Noport View Post
just for grins try this opcode OP_LootComplete=0x528f see if that helps
while i've not used perl to send packets to clients, i'm not sure how it would help in this case. please explain.

UPDATE: i wrote the following test plugin to make absolutely certain the issue wasn't with the slot numbers

Code:
sub DeleteAllLoot {

    my $corpse      = NULL;
    my $npc         = plugin::val('$npc');
    my $entity_list = plugin::val('$entity_list');

    foreach $corpse ($entity_list->GetCorpseList()) {
        if ($corpse->GetOwnerName() eq $npc->GetName() && $corpse->CountItems()) {
            for (my $slot = 0; $slot >= $corpse->CountItems(); $slot++) {
                $corpse->RemoveItem($slot);
            }
            return 1;
        }
    }

    return 0;

}
i know it's finding the correct corpse, but RemoveItem() is not deleting any items from NPC corpses.
Reply With Quote
  #6  
Old 02-16-2012, 02:46 AM
lerxst2112
Demi-God
 
Join Date: Aug 2010
Posts: 1,743
Default

Well, let's look at the code, it's pretty short and simple.

Code:
void Corpse::RemoveItem(int16 lootslot)
{

	if (lootslot == 0xFFFF)
		return;
	
	ItemList::iterator cur,end;
	cur = itemlist.begin();
	end = itemlist.end();
	for(; cur != end; cur++) {
		ServerLootItem_Struct* sitem = *cur;
		if (sitem->lootslot == lootslot)
		{
			RemoveItem(sitem);
			return;
		}
	}
}
Ok, it's looking for the slot number you pass in to match the lootslot on the ServerLootItem. So, let's look where that is assigned.

The only place I can see is Corpse::MakeLootRequestPackets where it is assigned 0xFFFF, and then a few lines later to a sequential value.

That function appears to be called when someone actually clicks on a corpse to loot it which may be later than you run your perl code.

NPC::AddLootDrop seems like a logical place to assign a value to ServerLootItem_Struct::lootslot, but I didn't see anything in there that would do so. ServerLootItem_Struct has no constructor, so what ends up in there by default is probably uninitialized garbage of some sort.

Short answer is I would guess that function isn't all that useful the way it is now. Maybe it should just take an index into the list of items and delete that index which is what it seems you want. Even if the code you're using is in EVENT_LOOT, I'm not sure it's possible to prevent the current item from being looted by deleting it, but that's just my guess.
Reply With Quote
  #7  
Old 02-16-2012, 04:16 AM
c0ncrete's Avatar
c0ncrete
Dragon
 
Join Date: Dec 2009
Posts: 719
Default

well hell, that makes PERFECT sense. now i'm not sure why the function was exposed to perl at all. i can't wrap my head around how it would be used. i'm going to go to sleep and see if i can't sort something out tomorrow. looks like the only way to do what i'm trying to do is to modify the source. i was trying to avoid that, but oh well. thanks.
Reply With Quote
  #8  
Old 02-16-2012, 04:22 AM
trevius's Avatar
trevius
Developer
 
Join Date: Aug 2006
Location: USA
Posts: 5,946
Default

If you are trying to delete items from the NPC when it dies during EVENT_DEATH or something like that directly on the NPC's perl script, you will need to be using $npc commands, not $corpse stuff. At that time, I don't think the $corpse entity exists yet for that NPC.

Under the NPC class, there is this quest object:
Code:
RemoveItem(item_id, quantity= 0, slot= 0)
Unfortunately, I don't see any quest object yet to find items or item slots on an NPC. One could certainly be created though easy enough I am sure. But, in the mean-time, if you know the item IDs that you want to delete, you could just do a for loop through each possible slot 0 to 25 or something and try to delete the item you want deleted. If you don't know what item ID that would be, it may get a little more complicated. You would just need to create an array of item IDs when someone hands items to the NPC then just run the for loop on each item ID in that array. Should work fine.

Removing it from the corpse itself would probably be easier, but you would need to do it from something like player.pl or something. Since you can't really use EVENT_LOOT for that (I don't think), you may just need to send a signal to another NPC when the main NPC is killed and then have a timer that waits a second (to allow the corpse to be created) then searches all corpses and removes those items at that time.
__________________
Trevazar/Trevius Owner of: Storm Haven
Everquest Emulator FAQ (Frequently Asked Questions) - Read It!
Reply With Quote
  #9  
Old 02-16-2012, 04:39 AM
c0ncrete's Avatar
c0ncrete
Dragon
 
Join Date: Dec 2009
Posts: 719
Default

EVENT_DEATH is parsed after the corpse created and the npc is removed from the entity list, at the tail end of NPC:eath(). the corpse's item list would already have been populated at that point. i'm probably going to have to add a function such as $corpse->RemoveItemByID() to be able to do what i'm looking to do. i'll have the item id stored in the script for the npc that i'll need to delete from the corpse. working on a method to generate random loot for npcs on spawn (in addition to normal loot table drops) and means to delete said randomly generated loot if the kill was trivial to anyone on the npc's hate list on death. everything else is working flawlessly except for this one part. for the time being, i have the corpse poofing entirely, but i'd rather just remove the one item.

EDIT:
hmmm... i guess i could always cause the item to poof after it was looted in EVENT_LOOT via player.pl, but that'd surely cause an uproar from the player base. haha.
Reply With Quote
  #10  
Old 02-16-2012, 05:10 AM
c0ncrete's Avatar
c0ncrete
Dragon
 
Join Date: Dec 2009
Posts: 719
Default

actually, if i moved the code that fires off EVENT_DEATH / EVENT_NPC_SLAY in the source up a bit to directly after experience and faction hits are given, and right before the corpse is created, the perl $npc methods would work perfectly. i don't see how that would really break anything with the source and it'd be a very minor change.

gah... i'm never going to get to sleep at this rate.
Reply With Quote
  #11  
Old 02-16-2012, 06:01 AM
trevius's Avatar
trevius
Developer
 
Join Date: Aug 2006
Location: USA
Posts: 5,946
Default

Ahh, I misunderstood your reason for wanting to remove the item. If you are adding them via a script, then you have full control over if they get added or not. Why not just add the item to the corpse after it dies or at the time of death or attack or any number of possible spots instead of adding it all on spawn? That will reduce the work load on the server a bit when your zone is spawned, since it won't be doing it for all NPCs at once, just when needed.

I have a somewhat similar plugin I use that adds loot when combat is started. Basically I use it to scale loot drop rates to the size of the group that engaged the NPC. The idea is to help promote grouping by giving increased quest item drop rates and such based on the size of the group so you aren't penalized by drops as much when grouping. People should be more willing to open up group slots to others if they know it won't take them 6X as long to get the drops they need.

This plugin does have a trivial loot code option if you set it. I can give an example of usage if you need. It could probably work to do what you are wanting without actually scaling the drop rates. Though, it looks like the trivial check wasn't added to the non-group sections of the plugin (as I don't use that option yet anyway), so you would need to modify it slightly.

Code:
###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");
					}
				}
			}
		}
	}
}
For what you are doing, something as simple as the below plugin is probably a better example. This one just for adding loot with a random chance:

Code:
###Usage: plugin::AddLoot( item_id, chance[1-100]);
# This plugin will just add loot with a chance
# If add rare chance field, can extend range past 100;

sub AddLoot {

	my $ItemId = $_[0];
	my $Chance = $_[1];
	my $Rare_Chance = $_[2];
	my $npc = plugin::val('$npc');

	my $Charge = 0;	
	my $Stack_Size = $npc->GetItemStat($ItemId, "stacksize"); 
	my $Has_Charge = $npc->GetItemStat($ItemId, "maxcharges"); 
	if ($Stack_Size >= 1) { $Charge = 1; }
	if ($Has_Charge >= 1) { $Charge = $Has_Charge; }
	
	if (!defined($Chance))
	{
		quest::addloot($ItemId, $Charge, 0);
	}
	
	if ((defined($Chance)) && (!defined($Rare_Chance)))
	{
		my $Get_Rand = plugin::RandomRange(1, 100);
		#plugin::Debug("Attempt :: Loot Add! $ItemId $Chance >= $Get_Rand");

		if ($Chance >= $Get_Rand)
		{
			quest::addloot($ItemId, $Charge, 0);
			#plugin::Debug("Loot Add! $ItemId");
		}
	}

	if ((defined($Chance)) && (defined($Rare_Chance)))
	{
		my $Get_Rand2 = plugin::RandomRange(1, $Rare_Chance);
		if ($Chance >= $Get_Rand2)
		{
			quest::addloot($ItemId, $Charge, 0);
			#plugin::Debug("Loot Add! $ItemId");
		}
	}
}
__________________
Trevazar/Trevius Owner of: Storm Haven
Everquest Emulator FAQ (Frequently Asked Questions) - Read It!

Last edited by trevius; 02-16-2012 at 06:06 AM..
Reply With Quote
  #12  
Old 02-16-2012, 11:02 AM
c0ncrete's Avatar
c0ncrete
Dragon
 
Join Date: Dec 2009
Posts: 719
Default

nice ideas there!

i think i'll tweak mine a bit to where the loot is added when the npc is engaged to deal with initial zone load times, scaling that chance based on character/group/raid makeup. absolutely brilliant.

i still want to have code to remove equipment on death if anything gets added to the npc's hate list that would cause the kill to be considered trivial for the following reasons:

1) the npc will equip the item, theoretically increasing encounter difficulty (do want)
2) if every npc in game has a chance at random loot, people will seek out greens (do not want)

i moved the triggers for EVENT_DEATH and EVENT_NPC_SLAY and started a compile earlier before i passed out for a few hours. i'm going to test it in a little while. this should keep me from having to iterate over the entire corpse list for a zone to find the corpse owned by the npc as well.
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 02:05 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