瀏覽代碼

* Fixed server processing the sell list (deleting items and giving zeny) for script-controlled shops (OnSellItem), causing the controller script to fail (bugreport:4656, since r6557).
- This also makes the server first check the sell list and only continue, if all items can be processed, thus no longer causing incomplete deals and the need for client disconnection in such case (since r6557).

git-svn-id: https://svn.code.sf.net/p/rathena/svn/trunk@14617 54d463be-8e91-2dee-dedb-b68131a5f0ec

ai4rei 14 年之前
父節點
當前提交
a45a6400e3
共有 2 個文件被更改,包括 107 次插入62 次删除
  1. 3 0
      Changelog-Trunk.txt
  2. 104 62
      src/map/npc.c

+ 3 - 0
Changelog-Trunk.txt

@@ -1,5 +1,8 @@
 Date	Added
 
+2010/12/23
+	* Fixed server processing the sell list (deleting items and giving zeny) for script-controlled shops (OnSellItem), causing the controller script to fail (bugreport:4656, since r6557). [Ai4rei]
+	- This also makes the server first check the sell list and only continue, if all items can be processed, thus no longer causing incomplete deals and the need for client disconnection in such case (since r6557).
 2010/12/22
 	* NPC shop buy list received from the client is now validated for scripted shops (OnBuyItem), before the control is transfered to the script, as well (bugreport:3680, since r5841, related r8525). [Ai4rei]
 	* Resolved random compiler warnings. [Ai4rei]

+ 104 - 62
src/map/npc.c

@@ -1371,97 +1371,139 @@ int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list)
 	return 0;
 }
 
-/*==========================================
- *
- *------------------------------------------*/
+
+/// npc_selllist for script-controlled shops
+static int npc_selllist_sub(struct map_session_data* sd, int n, unsigned short* item_list, struct npc_data* nd)
+{
+	char npc_ev[NAME_LENGTH*2+3];
+	int i, idx;
+	int key_nameid = 0;
+	int key_amount = 0;
+
+	// discard old contents
+	script_cleararray_pc(sd, "@sold_nameid", (void*)0);
+	script_cleararray_pc(sd, "@sold_quantity", (void*)0);
+
+	// save list of to be sold items
+	for( i = 0; i < n; i++ )
+	{
+		idx = item_list[i*2]-2;
+
+		script_setarray_pc(sd, "@sold_nameid", i, (void*)(intptr)sd->status.inventory[idx].nameid, &key_nameid);
+		script_setarray_pc(sd, "@sold_quantity", i, (void*)(intptr)item_list[i*2+1], &key_amount);
+	}
+
+	// invoke event
+	snprintf(npc_ev, ARRAYLENGTH(npc_ev), "%s::OnSellItem", nd->exname);
+	npc_event(sd, npc_ev, 0);
+	return 0;
+}
+
+
+/// Player item selling to npc shop.
+///
+/// @param item_list 'n' pairs <index,amount>
+/// @return result code for clif_parse_NpcSellListSend
 int npc_selllist(struct map_session_data* sd, int n, unsigned short* item_list)
 {
 	double z;
 	int i,skill;
-	int key_nameid = 0;
-	int key_amount = 0;
 	struct npc_data *nd;
-	
+
 	nullpo_retr(1, sd);
 	nullpo_retr(1, item_list);
 
-	if ((nd = npc_checknear(sd,map_id2bl(sd->npc_shopid))) == NULL)
+	if( ( nd = npc_checknear(sd, map_id2bl(sd->npc_shopid)) ) == NULL || nd->subtype != SHOP )
+	{
 		return 1;
-	nd = nd->master_nd; //For OnSell triggers.
+	}
 
-	if( nd )
+	z = 0;
+
+	// verify the sell list
+	for( i = 0; i < n; i++ )
 	{
-		// discard old contents
-		script_cleararray_pc(sd, "@sold_nameid", (void*)0);
-		script_cleararray_pc(sd, "@sold_quantity", (void*)0);
-	}
+		int nameid, amount, idx, value;
 
-	for(i=0,z=0;i<n;i++) {
-		int nameid, idx;
-		short qty;
-		idx = item_list[i*2]-2;
-		qty = (short)item_list[i*2+1];
-		
-		if (idx <0 || idx >=MAX_INVENTORY || qty < 0)
-			break;
-		
-		nameid=sd->status.inventory[idx].nameid;
-		if (nameid == 0 || !sd->inventory_data[idx] ||
-		   sd->status.inventory[idx].amount < qty)
-			break;
-		
-		z+=(double)qty*pc_modifysellvalue(sd,sd->inventory_data[idx]->value_sell);
+		idx    = item_list[i*2]-2;
+		amount = item_list[i*2+1];
 
-		if(sd->inventory_data[idx]->type == IT_PETEGG &&
-			sd->status.inventory[idx].card[0] == CARD0_PET)
+		if( idx >= MAX_INVENTORY || idx < 0 || amount < 0 )
 		{
-			if(search_petDB_index(sd->status.inventory[idx].nameid, PET_EGG) >= 0)
-				intif_delete_petdata(MakeDWord(sd->status.inventory[idx].card[1],sd->status.inventory[idx].card[2]));
+			return 1;
+		}
+
+		nameid = sd->status.inventory[idx].nameid;
+
+		if( !nameid || !sd->inventory_data[idx] || sd->status.inventory[idx].amount < amount )
+		{
+			return 1;
+		}
+
+		if( nd->master_nd )
+		{// Script-controlled shops decide by themselves, what can be sold and at what price.
+			continue;
 		}
 
-		if(log_config.enable_logs&0x20) //Logs items, Sold to NPC (S)hop [Lupus]
-			log_pick_pc(sd, "S", nameid, -qty, &sd->status.inventory[idx]);
+		value = pc_modifysellvalue(sd, sd->inventory_data[idx]->value_sell);
+
+		z+= (double)value*amount;
+	}
+
+	if( nd->master_nd )
+	{// Script-controlled shops
+		return npc_selllist_sub(sd, n, item_list, nd->master_nd);
+	}
+
+	// delete items
+	for( i = 0; i < n; i++ )
+	{
+		int nameid, amount, idx;
+
+		idx    = item_list[i*2]-2;
+		amount = item_list[i*2+1];
+		nameid = sd->status.inventory[idx].nameid;
+
+		//Logs items, Sold to NPC (S)hop [Lupus]
+		if( log_config.enable_logs&0x20 )
+			log_pick_pc(sd, "S", nameid, -amount, &sd->status.inventory[idx]);
+		//Logs
 
-		if( nd )
+		if( sd->inventory_data[idx]->type == IT_PETEGG && sd->status.inventory[idx].card[0] == CARD0_PET )
 		{
-			script_setarray_pc(sd, "@sold_nameid", i, (void*)(intptr)sd->status.inventory[idx].nameid, &key_nameid);
-			script_setarray_pc(sd, "@sold_quantity", i, (void*)(intptr)qty, &key_amount);
+			if( search_petDB_index(sd->status.inventory[idx].nameid, PET_EGG) >= 0 )
+			{
+				intif_delete_petdata(MakeDWord(sd->status.inventory[idx].card[1], sd->status.inventory[idx].card[2]));
+			}
 		}
-		pc_delitem(sd,idx,qty,0,6);
+
+		pc_delitem(sd, idx, amount, 0, 6);
 	}
 
-	if (z > MAX_ZENY) z = MAX_ZENY;
+	if( z > MAX_ZENY )
+		z = MAX_ZENY;
 
-	if(log_config.zeny) //Logs (S)hopping Zeny [Lupus]
+	//Logs (S)hopping Zeny [Lupus]
+	if( log_config.zeny )
 		log_zeny(sd, "S", sd, (int)z);
+	//Logs
 
-	pc_getzeny(sd,(int)z);
-	
-	if (battle_config.shop_exp > 0 && z > 0 && (skill = pc_checkskill(sd,MC_OVERCHARGE)) > 0) {
-		if (sd->status.skill[MC_OVERCHARGE].flag != 0)
+	pc_getzeny(sd, (int)z);
+
+	// custom merchant shop exp bonus
+	if( battle_config.shop_exp > 0 && z > 0 && ( skill = pc_checkskill(sd,MC_OVERCHARGE) ) > 0)
+	{
+		if( sd->status.skill[MC_OVERCHARGE].flag != 0 )
 			skill = sd->status.skill[MC_OVERCHARGE].flag - 2;
-		if (skill > 0) {
+		if( skill > 0 )
+		{
 			z = z * (double)skill * (double)battle_config.shop_exp/10000.;
-			if (z < 1)
+			if( z < 1 )
 				z = 1;
-			pc_gainexp(sd,NULL,0,(int)z, false);
+			pc_gainexp(sd, NULL, 0, (int)z, false);
 		}
 	}
-		
-	if(nd) {
-		char npc_ev[NAME_LENGTH*2+3];
-		snprintf(npc_ev, ARRAYLENGTH(npc_ev), "%s::OnSellItem", nd->exname);
-		npc_event(sd, npc_ev, 0);
-	}
-	
-	if (i<n) {
-		//Error/Exploit... of some sort. If we return 1, the client will not mark
-		//any item as deleted even though a few were sold. In such a case, we
-		//have no recourse but to kick them out so their inventory will refresh
-		//correctly on relog. [Skotlex]
-		if (i) set_eof(sd->fd);
-		return 1;
-	}
+
 	return 0;
 }