PDA

View Full Version : Nice quest npcs?


sotonin
08-04-2004, 04:50 PM
Has anybody discovered a way to make questgivers more like live. As in if you hand in an item that is not in any way a quest item, it says no thanks i dont need that and hands it back to you?

Because i've noticed that you have to hand in ALL items for a quest at once, else the questgivers just keep your stuff. So it would be very cool to somehow stop them from eating items.

killspree
08-04-2004, 05:26 PM
try
sub EVENT_ITEM{
if($itemcount{itemid} == 0 && $item1 > 0) {quest::say("I'm sorry, I don't need that item, $name. Here, you can have it back."); quest::summonitem("$item1");}
}

You'll need to expand it if you require multiple items for a turnin so that it returns all items in the turnin, obviously, but I've tested it and that does indeed work.

killspree
08-04-2004, 05:35 PM
expanded for convenience sake, enjoy:

sub EVENT_ITEM{
if($itemcount{itemid} == 0){
quest::say("I'm sorry, I don't need that item, $name. Here, you can have it back.");
if($item1 > 0){quest::summonitem("$item1");}
if($item2 > 0){quest::summonitem("$item2");}
if($item3 > 0){quest::summonitem("$item3");}
if($item4 > 0){quest::summonitem("$item4");}}
}

Just add as many $itemcount checks as you have items for the quest.

Charmy
08-04-2004, 11:02 PM
In all my quests i add similar code like that to a seperate sub, that way if the mob is involved in quests, you can use the effect several times without having to retype it.

e.g

sub EVENT_ITEM
{
if($itemcount {xx} && $itemcount {xxxx} == 1)
{
quest::say("Thanks for the stuff i needed $name.");
}
else
{
NoNeed();
}
if($itemcount {xx} && $itemcount {xxxx} == 1)
{
quest::say('thanks for this name);
}
else
{
NoNeed();
}
}

sub NoNeed
{
if($item1 != "")
{
quest::summonitem($item1,0);
}
if($item2 != "")
{
quest::summonitem($item2,0);
}
if($item3 != "")
{
quest::summonitem($item3,0);
}
if($item4 != "")
{
quest::summonitem($item4,0);
}
if($platnium != 0 || $gold != 0 || $silver != 0 || $copper != 0)
{
quest::givecash($platnium, $gold,$silver,$copper);
}


You Could also just make the second if an elsif, that way you only need one call of the NoNeed. but this was just a simple example.

killspree
08-04-2004, 11:25 PM
Hmm, charges set to 0 still summons an item? Or did you just put 0 there as an example?

cofruben
08-05-2004, 02:24 AM
sub EVENT_ITEM
{
if($item1==30){ quest::say("thanks"); }
else if($item1==23){ quest::say("thanks"); }
else if($item1==55){ quest::say("thanks"); }
else { quest::say("sorry i dont need this"); quest::summonitem($itemid); }
}
for example :wink:

sotonin
08-05-2004, 02:33 AM
hmmm interesting.

here's the problem though. I want the npc to
A) return item to player if they only hand in a single item from a multi-item turn in.
B) return any non-quest related item handed to him

here's what i have going so far, lemme know if i screwed anything up. I was thinking if there was some way to make a temporary variable. i could set a variable to true or 1 or whatever if any of the successful item turn in's are used. (item1 2 and 3 for quest are turned in, so set variable to true.) then below all the checks, do a if variable != true say "i dont need this, blah blah" and return all items in all 4 slots to the player.

sub EVENT_ITEM {
if($item1 > 0 || $item2 > 0 || $item3 > 0 || $item4 > 0){
if($itemcount{19001} == 1 && $itemcount{19002} == 1 && $itemcount{16507} == 1){
quest::say("Wear this with pride!");
quest::summonitem("4921");
}
if($itemcount{19003} == 1 && $itemcount{19004} == 1 && $itemcount{19047} == 1){
quest::say("Wear this with pride!");
quest::summonitem("4922");
}
if($itemcount{19005} == 1 && $itemcount{19006} == 1 && $itemcount{19048} == 1){
quest::say("Wear this with pride!");
quest::summonitem("4923");
}
if($itemcount{19007} == 1 && $itemcount{19008} == 1 && $itemcount{19049} == 1){
quest::say("Wear this with pride!");
quest::summonitem("4924");
}
}
}


i don't know exact perl syntax for this but here's my best complete guess

sub EVENT_ITEM {
if($item1 > 0 || $item2 > 0 || $item3 > 0 || $item4 > 0){
if($itemcount{19001} == 1 && $itemcount{19002} == 1 && $itemcount{16507} == 1){
quest::say("Wear this with pride!");
quest::summonitem("4921");
$var1=true;
}
if($itemcount{19003} == 1 && $itemcount{19004} == 1 && $itemcount{19047} == 1){
quest::say("Wear this with pride!");
quest::summonitem("4922");
$var1=true;
}
if($itemcount{19005} == 1 && $itemcount{19006} == 1 && $itemcount{19048} == 1){
quest::say("Wear this with pride!");
quest::summonitem("4923");
$var1=true;
}
if($itemcount{19007} == 1 && $itemcount{19008} == 1 && $itemcount{19049} == 1){
quest::say("Wear this with pride!");
quest::summonitem("4924");
$var1=true;
}
if ($var1!='true'){
if($item1 > 0){
quest::say("I don't need this.");
quest::summonitem("$item1");
}
if($item2 > 0){
quest::say("I don't need this.");
quest::summonitem("$item2");
}
if($item3 > 0){
quest::say("I don't need this.");
quest::summonitem("$item3");
}
if($item4 > 0){
quest::say("I don't need this.");
quest::summonitem("$item4");
}
}
}
}

sotonin
08-05-2004, 02:48 AM
ya, i think i'm stupid.

i shortened it i think. )

Any glaring problems with this?

sub EVENT_ITEM {
if($itemcount{19001} == 1 && $itemcount{19002} == 1 && $itemcount{16507} == 1){
quest::say("Wear this with pride!");
quest::summonitem("4921");
} else
if($itemcount{19003} == 1 && $itemcount{19004} == 1 && $itemcount{19047} == 1){
quest::say("Wear this with pride!");
quest::summonitem("4922");
} else
if($itemcount{19005} == 1 && $itemcount{19006} == 1 && $itemcount{19048} == 1){
quest::say("Wear this with pride!");
quest::summonitem("4923");
} else
if($itemcount{19007} == 1 && $itemcount{19008} == 1 && $itemcount{19049} == 1){
quest::say("Wear this with pride!");
quest::summonitem("4924");
} else {
quest::say("I don't need this...");
if($item1 > 0){quest::summonitem("$item1");}
if($item2 > 0){quest::summonitem("$item2");}
if($item3 > 0){quest::summonitem("$item3");}
if($item4 > 0){quest::summonitem("$item4");}
}
}

sotonin
08-05-2004, 12:43 PM
Welp. i found a biggy wrong with it. Seems cofruben's little example wasnt proper perl syntax. i changed it and it started working )

sub EVENT_ITEM {
if($itemcount{19001} == 1 && $itemcount{19002} == 1 && $itemcount{16507} == 1){
quest::say("Wear this with pride!");
quest::summonitem("4921");
} elsif($itemcount{19003} == 1 && $itemcount{19004} == 1 && $itemcount{19047} == 1){
quest::say("Wear this with pride!");
quest::summonitem("4922");
} elsif($itemcount{19005} == 1 && $itemcount{19006} == 1 && $itemcount{19048} == 1){
quest::say("Wear this with pride!");
quest::summonitem("4923");
} elsif($itemcount{19007} == 1 && $itemcount{19008} == 1 && $itemcount{19049} == 1){
quest::say("Wear this with pride!");
quest::summonitem("4924");
} else {
quest::say("I don't need this.");
if($item1 > 0){quest::summonitem("$item1");}
if($item2 > 0){quest::summonitem("$item2");}
if($item3 > 0){quest::summonitem("$item3");}
if($item4 > 0){quest::summonitem("$item4");}
}
}


The } elseif portion is what was screwing up before when it was }else if {

Charmy
08-05-2004, 12:45 PM
elseif isn't part of perl, its elsif, you will get complie errors if you use elseif.

sotonin
08-05-2004, 12:46 PM
oops sorry didnt read close enough.

yep typo on last part for me. code has it right though elsif

animepimp
08-05-2004, 04:18 PM
if($item1 > 0){quest::summonitem("$item1");}
if($item2 > 0){quest::summonitem("$item2");}
if($item3 > 0){quest::summonitem("$item3");}
if($item4 > 0){quest::summonitem("$item4");}

This part is wrong. You need to remove the quotes in the method calls, like quest::summonitem($item4); Or it will try to find "$item4" in a Db instead of the number stored in item4.

sotonin
08-05-2004, 05:25 PM
well it seems to work fine.... i use ""'s on all of my summonitems and they put the item on your cursor.

isn't that how summonitem is supposed to work? I tested all the quests and they all use quotes.

Cisyouc
08-06-2004, 03:23 AM
well it seems to work fine.... i use ""'s on all of my summonitems and they put the item on your cursor.

isn't that how summonitem is supposed to work? I tested all the quests and they all use quotes.

I always do quest::summonitem(0000); and it puts it on the cursor fine. If it doesnt there's a problem with the code above it.

sotonin
08-06-2004, 03:40 AM
aye. i use quest::summonitem("00000");

it puts the item on my cursor as well. So i guess animepimp is wrong about this syntax being wrong. =)

animepimp
08-06-2004, 03:51 AM
I know that "0000" will wokr perfectly fine, but "$item4" will not. Because if 1234 is stored in $item4 then putting ($item4) will be treated as (1234) but putting ("$item4") will search for "$item4" in the DB. It doesn't parse variables inside of quotes, anythigng in quotes is just copyed over.

killspree
08-06-2004, 04:06 AM
That part isn't wrong. Test it. I have, and it works fine.

sotonin
08-06-2004, 04:16 AM
works fine for me too.

perl parses the $item1 into the proper number before it's processed.

govtcheeze
08-06-2004, 05:14 AM
Anime are you confusing single quotes with double quotes? Single quotes work the way you explained in most languges...there is no variable expansion and the string is treated as a literal.

:?:

Charmy
08-06-2004, 05:57 AM
You goof AP, how do you think that Mobs return the class, or names of PCs?

quest::say("Hello $name, i see you are an $class, and a $race no less! thats a shame.");

For an iksar wizard would return

Hello Ikky, i see you are a Wizard, and an Iksar no less! thats a shame.

animepimp
08-06-2004, 02:36 PM
Guess I'm jsut getting my languages confused, sorry. I know 5 different programming languages and sometimes it all gets smeared together.

jimbox114
08-08-2004, 11:28 AM
I found this is a good script to put in the default.pl for the entire server. I am just wondering if there is a way to keep them from accepting items and storeing them. Even though you get the item back with this code, if you do a npcstats on them you will see they are holding it. I know this is a huge loop hole for cheaters to transfer no drop items to other players.

sotonin
08-08-2004, 11:30 AM
ah crap you're right i didn't think of that...

hmm

animepimp
08-08-2004, 11:49 AM
I guess we need a quest method that can manipulate the items the NPC is carrying. This would be useful for clearing their inventory in this case and also adding new loot if a certain quest is fufilled. Perhaps the code can be changed so it never adds stuff they are handed to their inventory? Should be pretty simple, jsut remove the right lines. ANd then a quest function can be written to add stuff to their inventory. Thats probably the best solution.

killspree
08-08-2004, 11:55 AM
Hmm...perhaps some code that deletes the item from the npc's loot table is in order. I'll take a look and see what I can come up with, thanks for bringing that up!

Facet42
08-08-2004, 11:58 AM
Maybe just get the code to hand back any item that isn't used by a quest by default and add a function to put an item into a mob's inventory via the quest system if it really is required?

animepimp
08-08-2004, 12:01 PM
But how would the code know that the item is not used by the quest? It just passes stuff to the quest and never knows if the variables get used or not.

killspree
08-08-2004, 12:02 PM
Well the problem with that is that it's the trade code that allows pets to use weapons, so I'll write up a temp fix for you guys to use while mulling over the options of how to fix it without borking that completely.

Facet42
08-08-2004, 12:09 PM
But how would the code know that the item is not used by the quest? It just passes stuff to the quest and never knows if the variables get used or not.

Is it not possible to return a value? 0 = item wasn't used, 1 = it was. Obviously, that would require 1 line quest rewrites, but not a huge hardship.

killspree
08-08-2004, 01:00 PM
Ok it looks like I'll need to dig a little deeper than expected. It appears the item doesn't go into the loot table of the NPC until after the trade...so the RemoveItem() function won't work in this case...at least not without some additional coding other than just adding another perl function.

Will post more when I get closer to a solution.

killspree
08-08-2004, 03:52 PM
I've come up with a "fix" for the problem of quest items sticking with an npc - it's a temp fix for now until I have more time to take a look at options to the "npc's can equip items" code. Basically what I did was add a new field to the npc_types index in the database called "questflag". I then added a function to check it and put it within the Client::FinishTrade function. It checks the questflag field in npc_types, and if 0 is returned, it allows the npc to have the item added to its loot table. If 1 is returned, the item is ignored(for loot table purposes - the npc still accepts the item through all tests I've done).

In client.cpp, replace Client::FinishTrade() with:
void Client::FinishTrade(NPC* with){
int32 items[4]={0};
int8 charges[4]={0};
for (sint16 i=3000; i<=3003; i++){
const ItemInst* inst = m_inv[i];
if (inst) {
items[i-3000]=inst->GetItem()->ItemNumber;
charges[i-3000]=inst->GetCharges();
DeleteItemInInventory(i);
}
}
char temp1[100];
memset(temp1,0x0,100);
char temp2[100];
memset(temp2,0x0,100);
for ( int z=0; z < 4; z++ ) {
sprintf(temp1,"item%d.%d", z+1,with->GetNPCTypeID());
sprintf(temp2,"%d",items[z]);
parse->AddVar(temp1,temp2);
memset(temp1,0x0,100);
memset(temp2,0x0,100);
}
sprintf(temp1,"copper.%d",with->GetNPCTypeID());
sprintf(temp2,"%i",trade->cp);
parse->AddVar(temp1,temp2);
memset(temp1,0x0,100);
memset(temp2,0x0,100);
sprintf(temp1,"silver.%d",with->GetNPCTypeID());
sprintf(temp2,"%i",trade->sp);
parse->AddVar(temp1,temp2);
memset(temp1,0x0,100);
memset(temp2,0x0,100);
sprintf(temp1,"gold.%d",with->GetNPCTypeID());
sprintf(temp2,"%i",trade->gp);
parse->AddVar(temp1,temp2);
memset(temp1,0x0,100);
memset(temp2,0x0,100);
sprintf(temp1,"platinum.%d",with->GetNPCTypeID());
sprintf(temp2,"%i",trade->pp);
parse->AddVar(temp1,temp2);
memset(temp1,0x0,100);
memset(temp2,0x0,100);
parse->Event(EVENT_ITEM, with->GetNPCTypeID(), 0, with, this->CastToMob());
LinkedListIterator<ServerLootItem_Struct*> iterator(*with->CastToNPC()->itemlist);
iterator.Reset();
int xy = 0;
while(iterator.MoreElements()) {
xy++;
iterator.Advance();
}

for(int y=0;y<4;y++){
if (xy <20){
xy++;
NPC* npc=with->CastToNPC();
const Item_Struct* item2 = database.GetItem(items[y]);
if (item2) { //no "no drop" items for j00!
ServerLootItem_Struct* item = new ServerLootItem_Struct;
item->item_id = item2->ItemNumber;
item->charges = charges[y];
char newid[20];
memset(newid, 0, sizeof(newid));
for(int i=0;i<7;i++){
if (!isalpha(item2->IDFile[i])){
strncpy(newid, &item2->IDFile[i],5);
i=8;
}
}
APPLAYER* outapp = new APPLAYER(OP_WearChange, sizeof(WearChange_Struct));
WearChange_Struct* wc = (WearChange_Struct*)outapp->pBuffer;
wc->spawn_id = npc->GetID();
wc->material=0;
if (((item2->EquipSlots==24576) || (item2->EquipSlots==8192)) && (npc->d_meele_texture1==0)) {
wc->wear_slot_id=7;
if (item2->Common.SpellId!=0)
npc->CastToMob()->AddProcToWeapon(item2->Common.SpellId,true);
npc->equipment[7]=item2->ItemNumber;
npc->d_meele_texture1=atoi(newid);
if (item2->Common.Material >0)
wc->material=item2->Common.Material;
else
wc->material=atoi(newid);
npc->AC+=item2->Common.AC;
npc->STR+=item2->Common.STR;
npc->INT+=item2->Common.INT;
}
else if (((item2->EquipSlots==24576) || (item2->EquipSlots==16384)) && (npc->d_meele_texture2 ==0) && ((npc->GetLevel()>=13) || (item2->Common.Damage==0)))
{
if (item2->Common.SpellId!=0)
npc->CastToMob()->AddProcToWeapon(item2->Common.SpellId,true);
npc->d_meele_texture2=atoi(newid);
npc->equipment[8]=item2->ItemNumber;
wc->wear_slot_id=8;
if (item2->Common.Material >0)
wc->material=item2->Common.Material;
else
wc->material=atoi(newid);
npc->AC+=item2->Common.AC;
npc->STR+=item2->Common.STR;
npc->INT+=item2->Common.INT;
}
else if ((item2->EquipSlots==4) && (npc->equipment[0]==0)){

npc->equipment[0]=atoi(newid);
if (item2->Common.Material >0)
wc->material=item2->Common.Material;
else
wc->material=atoi(newid);
wc->wear_slot_id=0;
npc->AC+=item2->Common.AC;
npc->STR+=item2->Common.STR;
npc->INT+=item2->Common.INT;
}
else if ((item2->EquipSlots==131072) && (npc->equipment[1]==0)){
npc->equipment[1]=atoi(newid);
if (item2->Common.Material >0)
wc->material=item2->Common.Material;
else
wc->material=atoi(newid);
wc->wear_slot_id=1;
npc->AC+=item2->Common.AC;
npc->STR+=item2->Common.STR;
npc->INT+=item2->Common.INT;
}
else if ((item2->EquipSlots==128) && (npc->equipment[2]==0)){
npc->equipment[2]=atoi(newid);
if (item2->Common.Material >0)
wc->material=item2->Common.Material;
else
wc->material=atoi(newid);
wc->wear_slot_id=2;
npc->AC+=item2->Common.AC;
npc->STR+=item2->Common.STR;

npc->INT+=item2->Common.INT;
}
else if ((item2->EquipSlots==1536) && (npc->equipment[3]==0)){
npc->equipment[3]=atoi(newid);
if (item2->Common.Material >0)
wc->material=item2->Common.Material;
else
wc->material=atoi(newid);
wc->wear_slot_id=3;
npc->AC+=item2->Common.AC;
npc->STR+=item2->Common.STR;
npc->INT+=item2->Common.INT;
}

else if ((item2->EquipSlots==4096) && (npc->equipment[4]==0)){
npc->equipment[4]=atoi(newid);
if (item2->Common.Material >0)
wc->material=item2->Common.Material;
else
wc->material=atoi(newid);
wc->wear_slot_id=4;
npc->AC+=item2->Common.AC;
npc->STR+=item2->Common.STR;
npc->INT+=item2->Common.INT;
}
else if ((item2->EquipSlots==262144) && (npc->equipment[5]==0)){
npc->equipment[5]=atoi(newid);
if (item2->Common.Material >0)

wc->material=item2->Common.Material;
else
wc->material=atoi(newid);
wc->wear_slot_id=5;
npc->AC+=item2->Common.AC;
npc->STR+=item2->Common.STR;
npc->INT+=item2->Common.INT;
}
else if ((item2->EquipSlots==524288) && (npc->equipment[6]==0)){
npc->equipment[6]=atoi(newid);
if (item2->Common.Material >0)
wc->material=item2->Common.Material;
else
wc->material=atoi(newid);
wc->wear_slot_id=6;
npc->AC+=item2->Common.AC;
npc->STR+=item2->Common.STR;


npc->INT+=item2->Common.INT;
}
if (((npc->GetRace()==127) && (npc->CastToMob()->GetOwnerID()!=0)) && (item2->EquipSlots==24576) || (item2->EquipSlots==8192) || (item2->EquipSlots==16384)){
npc->d_meele_texture2=atoi(newid);
wc->wear_slot_id=8;
if (item2->Common.Material >0)
wc->material=item2->Common.Material;
else
wc->material=atoi(newid);
npc->AC+=item2->Common.AC;
npc->STR+=item2->Common.STR;
npc->INT+=item2->Common.INT;
}
item->equipSlot = item2->EquipSlots;
if((item2->NoDrop != 0 && !parse->HasQuestFile(with->GetNPCTypeID())) || this->GetGM())
if(GetQuestNPCFlag(CastToClient()->GetTarget()->CastToNPC()->GetNPCTypeID()) == 0){//Killspree: QuestNPC check
(*npc->itemlist).Append(item);//npc isn't a quest npc, let it have the item
entity_list.QueueClients(this, outapp);
safe_delete(outapp);
}
else{
entity_list.QueueClients(this, outapp);
safe_delete(outapp);
//Killspree: This npc is a quest npc, letting it keep items can be bad for duping!
}
}
}
}
}

Now above/below that add:
int8 GetQuestNPCFlag(int32 npcid){
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
MYSQL_RES *result;
MYSQL_ROW row;
if (database.RunQuery(query, MakeAnyLenString(&query, "SELECT questflag FROM npc_types WHERE id=%i",npcid), errbuf, &result)) {
safe_delete_array(query);
if (mysql_num_rows(result) == 1)
{
row = mysql_fetch_row(result);
int8 questflag = atoi(row[0]);
mysql_free_result(result);
return questflag;
}
else
{
mysql_free_result(result);
return 0;
}
mysql_free_result(result);
}
else
{
cerr << "Error in GetQuestNPCFlag query '" << query << "' " << errbuf << endl;
safe_delete_array(query);
return false;
}

return 0;
}

There you have it. If you have any problems with the code, please let me know and I'll take a look at it. If you have any suggestions on where better to place it by all means speak up on that as well.

For the database portion, simply type the following:
ALTER TABLE `npc_types` ADD `questflag` TINYINT(4) DEFAULT "0" NOT NULL AFTER `AC`

There you go. You'll have to set the "questflag" field to 1 for any NPCs you don't want to keep items given to them, default is 0.

killspree
08-10-2004, 06:06 AM
Anyone tried this yet? Curious if it's working ok.