Jump to content
  • 0
Sign in to follow this  
GmOcean

NPC Buy/Sell

Question

Edit: Hiding the post in a spoiler since this is solved already.

But here is the patch in case anyone wants to try and use it: buybackfeatureV0.1.patch

 

Don't forget to add this table to your sql database.

CREATE TABLE `hercules_mainsql`.`buyback` (  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,  `char_id` INT(11) UNSIGNED NOT NULL,  `nameid` INT(11) UNSIGNED NOT NULL DEFAULT 501,  `amount` INT(11) UNSIGNED NOT NULL DEFAULT 1,  `identify` INT(11) UNSIGNED NOT NULL DEFAULT 1,  `refine` INT(11) UNSIGNED NOT NULL DEFAULT 0,  `attribute` INT(11) UNSIGNED NOT NULL DEFAULT 0,  `card1` INT(11) UNSIGNED NOT NULL DEFAULT 0,  `card2` INT(11) UNSIGNED NOT NULL DEFAULT 0,  `card3` INT(11) UNSIGNED NOT NULL DEFAULT 0,  `card4` INT(11) UNSIGNED NOT NULL DEFAULT 0,  `bound` INT(11) UNSIGNED NOT NULL DEFAULT 0,  `expire_time` INT(11) UNSIGNED NOT NULL DEFAULT 0,  PRIMARY KEY (`id`));

.... Maybe in the future i'll see about just placing this in SRC release D: till then, I feel it's not quite up-to par to be a release lol.

 

Currently NPC's can only display a normal item in the BUY window, meaning it's currently not possible to buy upgraded/carded items through a normal NPC shop without doing something like a dynamic shop.
However, the client is capable of displaying the item's true information when attempting to sell to an npc.
 
So, really I'm trying to figure out how to get the client to display the items in the shop windows, if I can find that then, it might be possible to create something like a buyback system on the src side rather than the script side, by creating a sql log that tracks when players sell items, and storing that information.
Only the last 10-20items will need to be stored, after that we just replace the list as we go. We could also maybe store this information on the server's ram as an alternative so that it wipes when the player logs rather than on a semi-permanent sql log.
 

 

void clif_buylist(struct map_session_data *sd, struct npc_data *nd) {	struct npc_item_list *shop = NULL;	unsigned short shop_size = 0;	int fd,i,c;	nullpo_retv(sd);	nullpo_retv(nd);	if( nd->subtype == SCRIPT ) {		shop = nd->u.scr.shop->item;		shop_size = nd->u.scr.shop->items;	} else {		shop = nd->u.shop.shop_item;		shop_size = nd->u.shop.count;	}	fd = sd->fd;	WFIFOHEAD(fd, 4 + shop_size * 11);	WFIFOW(fd,0) = 0xc6;	c = 0;	for( i = 0; i < shop_size; i++ ) {		if( shop[i].nameid ) {			struct item_data* id = itemdb->exists(shop[i].nameid);			int val = shop[i].value;			if( id == NULL )				continue;			WFIFOL(fd, 4+c*11) = val;			WFIFOL(fd, 8+c*11) = pc->modifybuyvalue(sd,val);			WFIFOB(fd,12+c*11) = itemtype(id->type);			WFIFOW(fd,13+c*11) = ( id->view_id > 0 ) ? id->view_id : id->nameid;			c++;		}	}	WFIFOW(fd,2) = 4 + c*11;	WFIFOSET(fd,WFIFOW(fd,2));}

Currently that snippet code block just searchs if the item is an actual item, where as the sell_list uses our inventory:

void clif_selllist(struct map_session_data *sd){	int fd,i,c=0,val;	nullpo_retv(sd);	fd=sd->fd;	WFIFOHEAD(fd, MAX_INVENTORY * 10 + 4);	WFIFOW(fd,0)=0xc7;	for( i = 0; i < MAX_INVENTORY; i++ )	{		if( sd->status.inventory[i].nameid > 0 && sd->inventory_data[i] )		{			if( !itemdb_cansell(&sd->status.inventory[i], pc_get_group_level(sd)) )				continue;			if( sd->status.inventory[i].expire_time )				continue; // Cannot Sell Rental Items 			if( sd->status.inventory[i].bound && !pc_can_give_bound_items(sd))				continue; // Don't allow sale of bound items			val=sd->inventory_data[i]->value_sell;			if( val < 0 )				continue;			WFIFOW(fd,4+c*10)=i+2;			WFIFOL(fd,6+c*10)=val;			WFIFOL(fd,10+c*10)=pc->modifysellvalue(sd,val);			c++;		}	}	WFIFOW(fd,2)=c*10+4;	WFIFOSET(fd,WFIFOW(fd,2));}

 


Seeing as how this is possible to have the npc display the information, how can I feed it information stored from a sql-table or temp-stored ram (preferred since this usually wipes on player logout), and would I need to make a new npc function in npc.c that tells the server what type of BUY npc it is (I'm assuming yes, since we did this for different type of shop npcs already like MARKET and CASHSHOP).
 
Edit:
Ultimately I suck at all things src related in RO, since I have like 1% knowledge in C. But I'm trying to learn, and I've been wanting this in RO for years now, figure I'd do my part and make an attempt instead of just waiting around being a leech !!. So help me out if you can, and let me know if it's not possible xD. Thanks everyone.

 

 

Edited by GmOcean

Share this post


Link to post
Share on other sites

23 answers to this question

Recommended Posts

  • 0

See the First post to get the patch file.

 

*Hiding this post in spoiler since patch is uploaded. But gonna keep this information here*

 

D: I think it's a well timed attempt for my first ever real source mod lol. But yeah, it's taking a lot longer than I thought, but that's mainly due to me wanting it all to be source side. Honestly, where it is at, I can easily just remove the limitations I put on it in the source, and allow it to function with the OnSellItem and OnBuyItem labels, and do the bulky half of the job with script. As it would be A LOT easier. But, I feel that it'd be a half-lazy mod if I did it like that :/.

 

Also, if I remove the 10 item limit the SQL script will run twice as fast, if not 3x faster. Since it won't have to do a double check and just insert. I'm thinking this might be the better idea, since I can limit the items given back on the script side. But the main reason I added the limit to begin with is, when giving the items to the player, their inventory could be nearly full, don't wanna give them 100 items if they can only hold like 20 lol. I'll post back with a few changes. It'll probably the last modification I do to it. After that, it'll be up to you or whomever wishes to use it to make the changes they want to it.

 

Edit:

My bad lol, I completely forgot to post back @.@ haha.

CLIF.C

void clif_buybacklist(struct map_session_data *sd){	int fd,i,c=0,val;	nullpo_retv(sd);	fd=sd->fd;	WFIFOHEAD(fd, MAX_INVENTORY * 10 + 4);	WFIFOW(fd,0)=0xc7;	for( i = 0; i < MAX_INVENTORY; i++ )	{		if( sd->status.inventory[i].nameid > 0 && sd->inventory_data[i] && sd->status.inventory[i].expire_time && sd->status.inventory[i].bound )		{			if( !itemdb_cansell(&sd->status.inventory[i], pc_get_group_level(sd)) )				continue;						//This allows the selling(Buyback) of items that have both an expiration time and are bound to the player ONLY. (Normally not possible).						val=sd->inventory_data[i]->value_buy;			if( val < 0 )				continue;			WFIFOW(fd,4+c*10)=i+2;			WFIFOL(fd,6+c*10)=val;			WFIFOL(fd,10+c*10)=pc->modifysellvalue(sd,val); //Can I remove this if my npc.c data doesn't use it?			c++;		}	}	WFIFOW(fd,2)=c*10+4;	WFIFOSET(fd,WFIFOW(fd,2));}
clif->buybacklist = clif_buybacklist;

 

 

NPC.C

int npc_buysellsel(struct map_session_data* sd, int id, int type) {	struct npc_data *nd;	nullpo_retr(1, sd);	if ((nd = npc->checknear(sd,map->id2bl(id))) == NULL)		return 1;	if ( nd->subtype != SHOP && !(nd->subtype == SCRIPT && nd->u.scr.shop && nd->u.scr.shop->items) ) {				if( nd->subtype == SCRIPT )			ShowError("npc_buysellsel: trader '%s' has no shop list!n",nd->exname);		else			ShowError("npc_buysellsel: no such shop npc %d (%s)n",id,nd->exname);		 		if (sd->npc_id == id)			sd->npc_id = 0;		return 1;	}    	if (nd->option & OPTION_INVISIBLE) // can't buy if npc is not visible (hack?)		return 1;		if( nd->class_ < 0 && !sd->state.callshop ) {// not called through a script and is not a visible NPC so an invalid call		return 1;	}	// reset the callshop state for future calls	sd->state.callshop = 0;	sd->npc_shopid = id;	if (type==0) {		clif->buylist(sd,nd);	} 	if (type==1) {		clif->selllist(sd);	} 	if (type==2) {		clif->buybacklist(sd);		pc->setregistry(sd, script->add_str( "buyback" ), 1); // Set buyback player variable to 1 to trigger buyback feature in shops.	}		return 0;}
int npc_selllist(struct map_session_data* sd, int n, unsigned short* item_list) {	double z;	int i,skill_t, skill_idx = skill->get_index(MC_OVERCHARGE);	struct npc_data *nd;	int w, new_; //BuyBack Feature	nullpo_retr(1, sd);	nullpo_retr(1, item_list);	if( ( nd = npc->checknear(sd, map->id2bl(sd->npc_shopid)) ) == NULL ) {		return 1;	}	if( nd->subtype != SHOP ) {		if( !(nd->subtype == SCRIPT && nd->u.scr.shop && nd->u.scr.shop->type == NST_ZENY) )			return 1;	}	if( pc_readglobalreg( sd, script->add_str( "buyback" ) ) ) {		w = 0;		new_ = 0;	}	z = 0;	// verify the sell list	for( i = 0; i < n; i++ ) {		int nameid, amount, idx, value;		idx    = item_list[i*2]-2;		amount = item_list[i*2+1];		if( idx >= MAX_INVENTORY || idx < 0 || amount < 0 ) {			return 1;		}		nameid = sd->status.inventory[idx].nameid;		if( !nameid || !sd->inventory_data[idx] || sd->status.inventory[idx].amount < amount ) {			return 1;		}		if( !pc_readglobalreg( sd, script->add_str( "buyback" ) ) ) { //Buyback feature doesn't support script-controlled shops. This is intended.			if( nd->master_nd ) {// Script-controlled shops decide by themselves, what can be sold and at what price.				continue;			}		}		if( pc_readglobalreg( sd, script->add_str( "buyback" ) ) ) {			value = sd->inventory_data[idx]->value_buy; //No Discount when buying back.			w+= itemdb_weight(nameid) * amount;		} else {			value = pc->modifysellvalue(sd, sd->inventory_data[idx]->value_sell);		}		z+= (double)value*amount;	}	if( !pc_readglobalreg( sd, script->add_str( "buyback" ) ) ) { //Only works if NOT BuyBack Feature this is intended.		if( nd->master_nd ) { // Script-controlled shops			return npc->selllist_sub(sd, n, item_list, nd->master_nd);		}	}	if( pc_readglobalreg( sd, script->add_str( "buyback" ) ) ) {				if( z > (double)sd->status.zeny )			return 1; // Not Enough Zeny		if( w + sd->weight > sd->max_weight )			return 2; // Too heavy		if( pc->inventoryblank(sd) < new_ )			return 3; // Not enough space to store items	pc->payzeny(sd,(int)z,LOG_TYPE_NPC, NULL);		// add items		for( i = 0; i < n; i++ ) {			int amount, idx;			struct item it;			idx    = item_list[i*2]-2;			amount = item_list[i*2+1];			it.nameid = sd->status.inventory[idx].nameid;			it.amount = sd->status.inventory[idx].amount;			it.equip = 0; //Don't equip.			it.identify = sd->status.inventory[idx].identify;			it.refine = sd->status.inventory[idx].refine;			it.attribute = sd->status.inventory[idx].attribute;			it.card[0] = sd->status.inventory[idx].card[0];			it.card[1] = sd->status.inventory[idx].card[1];			it.card[2] = sd->status.inventory[idx].card[2];			it.card[3] = sd->status.inventory[idx].card[3];			it.expire_time = sd->status.inventory[idx].expire_time; // Normally can't sell Rental Items.			it.bound = sd->status.inventory[idx].bound; // Normally can't sell Bound Items.			//Do the below so player has enough inventory space. (Probably not needed since we do the check above).			if( it.amount == amount ) { // Check to see if they are buying ALL of that item. If so, DELETE entry from SQL table.                if( SQL->Query( map->mysql_handle, "DELETE FROM buyback WHERE char_id = %d AND nameid = %d AND amount = %d AND identify = %d AND refine = %d AND attribute = %d AND card1 = %d AND card2 = %d AND card3 = %d AND card4 = %d AND bound = %d AND expire_time = %d",                    sd->status.char_id,                    it.nameid,//sd->status.inventory[idx].nameid,                    it.amount,//sd->status.inventory[idx].amount,                    it.identify,//sd->status.inventory[idx].identify,                    it.refine,//sd->status.inventory[idx].refine,                    it.attribute,//sd->status.inventory[idx].attribute,                    it.card[0],//sd->status.inventory[idx].card[1],                    it.card[1],//sd->status.inventory[idx].card[2],                    it.card[2],//sd->status.inventory[idx].card[3],                    it.card[3],//sd->status.inventory[idx].card[4],                    0, // Should be 0 due to being unable to sell Bound Items normally                    0 // Should be 0 due to being unable to sell Rental Items normally                    ) == SQL_ERROR ) { Sql_ShowDebug( map->mysql_handle ); }            } else { // If they did not buy ALL of the item, UPDATE SQL table to display new amount.                if( SQL->Query( map->mysql_handle, "UPDATE buyback SET amount = %d WHERE char_id = %d AND nameid = %d AND amount = %d AND identify = %d AND refine = %d AND attribute = %d AND card1 = %d AND card2 = %d AND card3 = %d AND card4 = %d AND bound = %d AND expire_time = %d",                    amount,                    sd->status.char_id,                    it.nameid,//sd->status.inventory[idx].nameid,                    it.amount,//sd->status.inventory[idx].amount,                    it.identify,//sd->status.inventory[idx].identify,                    it.refine,//sd->status.inventory[idx].refine,                    it.attribute,//sd->status.inventory[idx].attribute,                    it.card[0],//sd->status.inventory[idx].card[1],                    it.card[1],//sd->status.inventory[idx].card[2],                    it.card[2],//sd->status.inventory[idx].card[3],                    it.card[3],//sd->status.inventory[idx].card[4],                    0, // Should be 0 due to being unable to sell Bound Items normally                    0 // Should be 0 due to being unable to sell Rental Items normally                    ) == SQL_ERROR ) { Sql_ShowDebug( map->mysql_handle ); }            }			pc->delitem(sd, idx, amount, 0, 6, LOG_TYPE_NPC); //Delete the Item With Expiration & Bound first.			it.expire_time = it.bound = 0; //Set Expiration and Bound to 0			pc->additem(sd, &it, amount, LOG_TYPE_NPC); // Add the item to inventory without Expiration and Bound		}	} else {	// delete items	for( i = 0; i < n; i++ ) {		int amount, idx; 		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( pet->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]));			}		}		//Store the Item into a SQL Table for SQL to use. (Need to figure out how to limit to last 10).		if( SQL->Query( map->mysql_handle, "INSERT INTO buyback ( char_id, nameid, amount, identify, refine, attribute, card1, card2, card3, card4, bound, expire_time ) VALUES ( %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d )",			sd->status.char_id,			sd->status.inventory[idx].nameid,			sd->status.inventory[idx].amount,			sd->status.inventory[idx].identify,			sd->status.inventory[idx].refine,			sd->status.inventory[idx].attribute,			sd->status.inventory[idx].card[0],			sd->status.inventory[idx].card[1],			sd->status.inventory[idx].card[2],			sd->status.inventory[idx].card[3],			sd->status.inventory[idx].bound,			sd->status.inventory[idx].expire_time			) == SQL_ERROR ) { Sql_ShowDebug( map->mysql_handle ); }		pc->delitem(sd, idx, amount, 0, 6, LOG_TYPE_NPC);	}	if( z > MAX_ZENY )		z = MAX_ZENY;	pc->getzeny(sd, (int)z, LOG_TYPE_NPC, NULL);	if( !pc_readglobalreg( sd, script->add_str( "buyback" ) ) ) { //BuyBack feature doesn't support shop EXP gain of any kind. This is an intended feature.		// custom merchant shop exp bonus		if( battle_config.shop_exp > 0 && z > 0 && ( skill_t = pc->checkskill2(sd,skill_idx) ) > 0) {			if( sd->status.skill[skill_idx].flag >= SKILL_FLAG_REPLACED_LV_0 )				skill_t = sd->status.skill[skill_idx].flag - SKILL_FLAG_REPLACED_LV_0;			if( skill_t > 0 ) {				z = z * (double)skill_t * (double)battle_config.shop_exp/10000.;				if( z < 1 )					z = 1;				pc->gainexp(sd, NULL, 0, (int)z, false);				}			}		}	}	pc->setregistry(sd, script->add_str( "buyback" ), 0); // Set buyback player variable to 0 to reset whether or not they are using a buyback shop	return 0;}

 

 

SCRIPT.C

/*========================================== * getbuybackitem * getbbitem *------------------------------------------*/BUILDIN(getbuybackitem) {	int nameid,amount,i,flag = 0, offset = 1;	int iden,ref,attr,c1,c2,c3,c4, bound = 0;	int expire;	TBL_PC *sd;	bound = script_getnum(st,11);	if( bound < IBT_MIN || bound > IBT_MAX ) { //Not a correct bound type		ShowError("script_getitembound2: Not a correct bound type! Type=%dn",bound);		return false;	}	if( script_hasdata(st,12+offset) )		sd=map->id2sd(script_getnum(st,12+offset)); // <Account ID>	else		sd=script->rid2sd(st); // Attached player	if( sd == NULL ) // no target		return true;	if( script_isstringtype(st, 2) ) {		const char *name = script_getstr(st, 2);		struct item_data *item_data = itemdb->search_name(name);		if( item_data )			nameid=item_data->nameid;		else			nameid=UNKNOWN_ITEM_ID;	} else {		nameid = script_getnum(st, 2);	}	amount=script_getnum(st,3);	iden=script_getnum(st,4);	ref=script_getnum(st,5);	attr=script_getnum(st,6);	c1=(short)script_getnum(st,7);	c2=(short)script_getnum(st,8);	c3=(short)script_getnum(st,9);	c4=(short)script_getnum(st,10);	expire = script_getnum(st,12);	if (bound && (itemdb_type(nameid) == IT_PETEGG || itemdb_type(nameid) == IT_PETARMOR)) {		ShowError("script_getitembound2: can't bind a pet egg/armor! Type=%dn",bound);		return false;	}	if(nameid<0) { // Invalide nameid		nameid = -nameid;		flag = 1;	}	if(nameid > 0) {		struct item item_tmp;		struct item_data *item_data = itemdb->exists(nameid);		int get_count;		memset(&item_tmp,0,sizeof(item_tmp));		if (item_data == NULL)			return -1;		if(item_data->type==IT_WEAPON || item_data->type==IT_ARMOR) {			if(ref > MAX_REFINE) ref = MAX_REFINE;		}		else if(item_data->type==IT_PETEGG) {			iden = 1;			ref = 0;		}		else {			iden = 1;			ref = attr = 0;		}		item_tmp.nameid=nameid;		if(!flag)			item_tmp.identify=iden;		else if(item_data->type==IT_WEAPON || item_data->type==IT_ARMOR)			item_tmp.identify=0;		item_tmp.refine=ref;		item_tmp.attribute=attr;		item_tmp.bound=(unsigned char)bound;		item_tmp.card[0]=(short)c1;		item_tmp.card[1]=(short)c2;		item_tmp.card[2]=(short)c3;		item_tmp.card[3]=(short)c4;		item_tmp.expire_time=(unsigned int)(time(NULL) + expire);		//Check if it's stackable.		if (!itemdb->isstackable(nameid))			get_count = 1;		else			get_count = amount;		for (i = 0; i < amount; i += get_count) {			// if not pet egg			if (!pet->create_egg(sd, nameid)) {				if ((flag = pc->additem(sd, &item_tmp, get_count, LOG_TYPE_SCRIPT))) {					clif->additem(sd, 0, 0, flag);					if( pc->candrop(sd,&item_tmp) )						map->addflooritem(&item_tmp,get_count,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0);				}			}		}	}	return true;}
BUILDIN_DEF(getbuybackitem, "viiiiiiiiii?"),BUILDIN_DEF2(getbuybackitem,"getbbitem","viiiiiiiiii?"),
/*========================================== * delbuybackitem * delbbitem *------------------------------------------*/BUILDIN(delbuybackitem) {	TBL_PC *sd;	struct item it;	if( script_hasdata(st,13) ) {		int account_id = script_getnum(st,13);		sd = map->id2sd(account_id); // <account id>		if( sd == NULL ) {			ShowError("script:delitem2: player not found (AID=%d).n", account_id);			st->state = END;			return false;		}	}	else	{		sd = script->rid2sd(st);// attached player		if( sd == NULL )			return true;	}	if( script_isstringtype(st, 2) ) {		const char* item_name = script_getstr(st, 2);		struct item_data* id = itemdb->search_name(item_name);		if( id == NULL ) {			ShowError("script:delitem2: unknown item "%s".n", item_name);			st->state = END;			return false;		}		it.nameid = id->nameid;// "<item name>"	} else {		it.nameid = script_getnum(st, 2);// <item id>		if( !itemdb->exists( it.nameid ) ) {			ShowError("script:delitem: unknown item "%d".n", it.nameid);			st->state = END;			return false;		}	}	it.amount=script_getnum(st,3);	it.identify=script_getnum(st,4);	it.refine=script_getnum(st,5);	it.attribute=script_getnum(st,6);	it.card[0]=(short)script_getnum(st,7);	it.card[1]=(short)script_getnum(st,8);	it.card[2]=(short)script_getnum(st,9);	it.card[3]=(short)script_getnum(st,10);	it.bound = script_getnum(st,11);	it.expire_time = script_getnum(st,12);	if( it.amount <= 0 )		return true;// nothing to do	if( script->buildin_delitem_search(sd, &it, true) )	{// success		return true;	}	ShowError("script:delitem2: failed to delete %d items (AID=%d item_id=%d).n", it.amount, sd->status.account_id, it.nameid);	st->state = END;	clif->scriptclose(sd, st->oid);	return false;}
BUILDIN_DEF(delbuybackitem, "viiiiiiiiii?"),BUILDIN_DEF2(delbuybackitem,"delbbitem","viiiiiiiiii?"),

 

 

 

SAMPLE SCRIPT

-	shop	BuyBack	-1,501:100prontera,154,176,4	script	BuyBackShop	123,{if( gettimetick(2) < @buybackshopdelay ) { dispbottom "You can not use this yet. Please wait, "+ (gettimetick(2) - @buybackshopdelay) +" more second(s)"; end; }if( select( "BuyBack Shop:Cancel" ) == 2 ) { close; }.@size = query_sql( "SELECT * FROM buyback WHERE char_id = "+ getcharid(0) +"", @row, @char_id, @id, @amt, @iden, @ref, @attr, @c1, @c2, @c3, @c4, @bound, @expire );if( !.@size ){ dispbottom "You haven't sold anything recently."; close; }@buybackshopdelay = gettimetick(2) + .expire;for( .@i = 0; .@i < getarraysize(@id); .@i++ ) {// Give player last sold items and bind them with a 30second expiration time.	getbuybackitem @id[.@i],@amt[.@i],@iden[.@i],@ref[.@i],@attr[.@i],@c1[.@i],@c2[.@i],@c3[.@i],@c4[.@i],.bound,.expire;//	dispbottom ""+ @id[.@i] +"";	}deletearray @id[0];callshop "BuyBack",3; end;OnInit:.bound = 4; //Type of Binding..expire = 15; // Time in seconds buyback shop returns item to player..logout_clear = 1; // 0 = Disable.  1 = Delete all buyback data upon character logout.end;OnPCLoginEvent:buyback = 0;end;OnPCLogoutEvent:if( .logout_clear ) { query_sql( "DELETE FROM buyback WHERE char_id = "+ getcharid(0) +"" ); }end;}

 


SQL TABLE

CREATE TABLE `hercules_mainsql`.`buyback` (  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,  `char_id` INT(11) UNSIGNED NOT NULL,  `nameid` INT(11) UNSIGNED NOT NULL DEFAULT 501,  `amount` INT(11) UNSIGNED NOT NULL DEFAULT 1,  `identify` INT(11) UNSIGNED NOT NULL DEFAULT 1,  `refine` INT(11) UNSIGNED NOT NULL DEFAULT 0,  `attribute` INT(11) UNSIGNED NOT NULL DEFAULT 0,  `card1` INT(11) UNSIGNED NOT NULL DEFAULT 0,  `card2` INT(11) UNSIGNED NOT NULL DEFAULT 0,  `card3` INT(11) UNSIGNED NOT NULL DEFAULT 0,  `card4` INT(11) UNSIGNED NOT NULL DEFAULT 0,  `bound` INT(11) UNSIGNED NOT NULL DEFAULT 0,  `expire_time` INT(11) UNSIGNED NOT NULL DEFAULT 0,  PRIMARY KEY (`id`));

 

 

Edit 2:

There is a couple small bugs I've noticed.

1. If a player "Splits" the amount of items he/she buys back the code is unable to detect it and will delete the entry from the SQL database regardless. *Fixed by adding an if( X == X ) statement.

2. Because I use the commands to give players the items with both bound and expire, the shop will give multiple items upon clicking close/cancel in the given time frames. *Fixed by adding shop use timer in script.

 

 

Edited by GmOcean

Share this post


Link to post
Share on other sites
  • 0

GmOcean, on 19 Sept 2014 - 19:31, said:

... and I've been wanting this in RO for years now...

LMAO !

I am sure you know when I started learn src edit, its from your topic (if eathena still up I can post the link haha)

I remember its ... *getdir script command

 

anyway, same as you, I also tried this once during eathena, and tried 1 more time 2~3 years back when during rathena ...

same result

 

because clif_selllist use the information directly from your inventory

 

if you want to display a signed item or carded item using clif_sellist,

then you also need to have that exact same item in your inventory

the code looks exactly similar to getinventorylist

loop all items in inventory, then check itemID + refine + attribute + identify + card0~3

if all condition same, then can show, otherwise, cannot show

 

so I did a trick

gives the signed item to the player (which display 'you get xxxx item' as dispbottom(blue) )

then open the shop, it works

if the player doesn't buy, delitem then close the shop

 

there are few problem with this

namely, checkweight, max inventory size, risk item drop on the floor ... and many

the player might also thought they got the item, but its actually just for show

 

 

 

I remember I also thought of another method

which is using auction menu

since auction allows to display signed/carded item as selling item, it might be just as possible to do this

but not so sure now because at that time I was using 20100730 hexed client, now the auction has been removed in latest client

Edited by AnnieRuru

Share this post


Link to post
Share on other sites
  • 0

If clif_selllist uses information from our inventory, that's completely voluntary on our part right? Could we not just " change " that 1 line and make it show something else? Then IF that is possible, (while being somewhat a little annoying) you can leave it as a sell button but send a buy packet instead D:. Or am I just jumping the gun and am overestimating the power of src code lol xD

Share this post


Link to post
Share on other sites
  • 0

Could we not just " change " that 1 line and make it show something else?

no

if you using clif_selllist function

then the packet header is 0xc7

this will open the sell item window

 

WFIFOW(fd,4+c*10)=i+2;

this one tells the client to list out the item from your inventory of position i

dunno what's +2 means though

 

try play around with it

Share this post


Link to post
Share on other sites
  • 0

Okay after " experimenting " with this a bit, it seems slightly plausible, but only through the use of a modified SELL window. This means that the player will ultimately have to click " sell ", but we could feed it data to have it return items instead.

Here's what I did so far:

 

 

clif.h

	void (*buylist) (struct map_session_data *sd, struct npc_data *nd);	void (*buybacklist) (struct map_session_data *sd); // BuyBack window list	void (*selllist) (struct map_session_data *sd);
	void (*pNpcBuyListSend) (int fd, struct map_session_data* sd);	void (*pNpcBuyBackListSend) (int fd, struct map_session_data* sd); // Buybacklist check	void (*pNpcSellListSend) (int fd,struct map_session_data *sd);

 

clif.c

void clif_buybacklist(struct map_session_data *sd){	int fd,i,c=0,val;	nullpo_retv(sd);	fd=sd->fd;	WFIFOHEAD(fd, MAX_INVENTORY * 10 + 4);	WFIFOW(fd,0)=0xc7;	for( i = 0; i < MAX_INVENTORY; i++ )	{		if( sd->status.inventory[i].nameid > 0 && sd->inventory_data[i] )		{			if( !itemdb_cansell(&sd->status.inventory[i], pc_get_group_level(sd)) )				continue;			if( sd->status.inventory[i].expire_time )				continue; // Cannot Sell Rental Items 			if( sd->status.inventory[i].bound && !pc_can_give_bound_items(sd))				continue; // Don't allow sale of bound items			val=sd->inventory_data[i]->value_sell;			if( val < 0 )				continue;			WFIFOW(fd,4+c*10)=i+2;			WFIFOL(fd,6+c*10)=val;			WFIFOL(fd,10+c*10)=pc->modifysellvalue(sd,val);			c++;		}	}	WFIFOW(fd,2)=c*10+4;	WFIFOSET(fd,WFIFOW(fd,2));}
/// 00c9 <packet len>.W { <index>.W <amount>.W }*void clif_parse_NpcBuyBackListSend(int fd,struct map_session_data *sd){	int fail=0,n;	unsigned short *item_list;	n = (RFIFOW(fd,2)-4) /4;	item_list = (unsigned short*)RFIFOP(fd,4);	if (sd->state.trading || !sd->npc_shopid)		fail = 1;	else		fail = npc->selllist(sd,n,item_list);	sd->npc_shopid = 0; //Clear shop data.	clif->npc_sell_result(sd, fail);}
	clif->buylist = clif_buylist;	clif->buybacklist = clif_buybacklist;	clif->selllist = clif_selllist;

 

npc.c

//Custom Sell List to return items instead of take items/// Player item selling to npc shop.////// @param item_list 'n' pairs <index,amount>/// @return result code for clif->parse_NpcSellListSendint npc_selllist(struct map_session_data* sd, int n, unsigned short* item_list) {	double z;	int i,w,new_, skill_idx = skill->get_index(MC_DISCOUNT);	struct npc_data *nd;	nullpo_retr(1, sd);	nullpo_retr(1, item_list);	if( ( nd = npc->checknear(sd, map->id2bl(sd->npc_shopid)) ) == NULL ) {		return 1;	}	if( nd->subtype != SHOP ) {		if( !(nd->subtype == SCRIPT && nd->u.scr.shop && nd->u.scr.shop->type == NST_ZENY) )			return 1;	}		z = 0;	w = 0;	new_ = 0;	// verify the sell list	for( i = 0; i < n; i++ ) {		int nameid, amount, idx, value;		idx    = item_list[i*2]-2;		amount = item_list[i*2+1];		if( idx >= MAX_INVENTORY || idx < 0 || amount < 0 ) {			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;		}		switch( pc->checkadditem(sd,nameid,amount) ) {			case ADDITEM_NEW:				new_++;				break;							case ADDITEM_OVERAMOUNT:				return 2;		}				value = sd->inventory_data[idx]->value_buy;//		value = pc->modifybuyvalue(sd, sd->inventory_data[idx]->value_buy);		z+= (double)value*amount;		w+= itemdb_weight(nameid) * amount;	}	if( nd->master_nd ) { // Script-controlled shops		return npc->selllist_sub(sd, n, item_list, nd->master_nd);	}		if( z > (double)sd->status.zeny )		return 1; // Not Enough Zeny	if( w + sd->weight > sd->max_weight )		return 2; // Too heavy	if( pc->inventoryblank(sd) < new_ )		return 3; // Not enough space to store items	pc->payzeny(sd,(int)z,LOG_TYPE_NPC, NULL);	// delete items	for( i = 0; i < n; i++ ) {		int nameid, amount, idx;		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( pet->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]));			}		} */ // Can't Buy pets with data (Don't wanna bother making this work, so it'll just be a fresh egg).				//Add pc->additem function	}	return 0;}

 

Right now, the main issue I'm having is the pc->additem function. If I take the code snippet from buylist, it does indeed add items, but it claims an unknown ID was used, so it creates a dummy item. I don't know how to make it grab the itemdata *item information from my inventory, without using sd->inventory_data[idx]. And obviously that won't work since I'm trying to feed it map_session data :/ (Why won't you just eat what I give you xD).

 

 

 

Edit:

 

Also, I know this is like 100% copy paste code, but I was able to replace the " buy feature " completely with the sell feature, since really the client just has buttons, the src does calculations, so from pressing BUY a brought up the window to sell items from my inventory. And it worked when attempting to sell.

 

However, the moment I tried to change it to creating a list of items to buy from my inventory it got very buggy. Items appeared as apples that ranged from -300mill to +300mill zeny, but when bought only gave me a magnifying glass ( was talking to a tool dealer located in prt_in ), for each item.

 

Knowing this, I believe we could do the following:

1. Add a new option to *callshop

*callshop "<name>",<option>;These are a series of commands used to create dynamic shops. The callshop function calls an invisible shop (view -1) as if the player clicked on it.For the options on callShop:	0 = The normal window (buy, sell and cancel)	1 = The buy window	2 = The sell window        3 = Buyback window

2. Then by modifying this:

int npc_buysellsel(struct map_session_data* sd, int id, int type) 

we can make it call our new clif->buybacklist

3. Do the following changes I made above ( of course with code from someone who can actually code LOL ).

 

4. Finally, see if it's possible for WFIFOW(fd,0)=0xc7; to display items listed from a different source then my inventory.

 

5. Find a way to have the client display BUY buttons instead of SELL buttons ( Seems like a minor issue to me ).

*Note - I can't remember if when replacing the clif->buylist function with clif->selllist  if it showed buy or sell buttons. All I know is, when prompted either ' Buy/Sell/Cancel ' it would bring up the window and display my inventory information.

 

Edited by GmOcean

Share this post


Link to post
Share on other sites
  • 0

Bumping this if I may.....

 

 

I've tried a few other things to get this to work, and failed sadly. Such as using map->charid2sd to try and obtain the players id, and thusly trying pc->additem(sd, sd->stautus.inventory[idx].nameid, etc...)

ultimately everything i've tried failed, because I don't know how to obtain and set the information I need to make it add items in a way that getitem2 does.

 

I'm thinking maybe I'll have to incorporate both script & src to make this work. But first there is the issue of making the shop generate an item list from sql data. Is this even possible? If not let me know, so this can be
scrapped already lol.

The reason I say this, is because, if a shop can indeed form a list of items to sell/buy from a sql table, then I can use the OnBuyItem/OnSellItem labels, and do the item calculation
there without the need to figure out pc->additem function in src. But I have no idea where to even begin when trying to pull data from a sql table let alone store it using src. I've tried looking at *query_sql in script.c but it just left me even more confused lol.

Share this post


Link to post
Share on other sites
  • 0

... trying pc->additem(sd, sd->stautus.inventory[idx].nameid, etc...)

ultimately everything i've tried failed, because I don't know how to obtain and set the information I need to make it add items in a way that getitem2 does.

if you look at BUILDIN(getitem)
pc->additem(sd, &it, get_count, LOG_TYPE_SCRIPT)
everything is manipulate in &it

where it comes from

struct item it;
and struct item comes from mmo.h
struct item {	int id;	short nameid;	short amount;	unsigned int equip; // Location(s) where item is equipped (using enum equip_pos for bitmasking).	char identify;	char refine;	char attribute;	short card[MAX_SLOTS];	unsigned int expire_time;	char favorite;	unsigned char bound;	uint64 unique_id;};
so you always manipulate the data with

it.identify = 1;

it.card[0] = 4001;

and so on

 

http://www.tutorialspoint.com/cprogramming/c_structures.htm

.

.

But I have no idea where to even begin when trying to pull data from a sql table let alone store it using src. I've tried looking at *query_sql in script.c but it just left me even more confused lol.

http://herc.ws/board/topic/7040-item-item-deny/?p=42843

need 3 commands

Sql->Query

Sql->NextRow

Sql->GetData

Edited by AnnieRuru

Share this post


Link to post
Share on other sites
  • 0

Okay, thank you Annieruru, that worked. At least I would go as far to say that I was able to finally make it give me the items displayed in the list. Now to tackle the issue of whether or not shops can indeed display data from a sql table. Here's the code block I used so far:

 

 

//Custom Sell List to return items instead of take items/// Player item selling to npc shop.////// @param item_list 'n' pairs <index,amount>/// @return result code for clif->parse_NpcSellListSendint npc_selllist(struct map_session_data* sd, int n, unsigned short* item_list) {	double z;	int i,w,new_, skill_idx = skill->get_index(MC_DISCOUNT);	struct npc_data *nd;	nullpo_retr(1, sd);	nullpo_retr(1, item_list);	if( ( nd = npc->checknear(sd, map->id2bl(sd->npc_shopid)) ) == NULL ) {		return 1;	}	if( nd->subtype != SHOP ) {		if( !(nd->subtype == SCRIPT && nd->u.scr.shop && nd->u.scr.shop->type == NST_ZENY) )			return 1;	}		z = 0;	w = 0;	new_ = 0;	// verify the sell list	for( i = 0; i < n; i++ ) {		int nameid, amount, idx, value;		idx    = item_list[i*2]-2;		amount = item_list[i*2+1];		if( idx >= MAX_INVENTORY || idx < 0 || amount < 0 ) {			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;		}		switch( pc->checkadditem(sd,nameid,amount) ) {			case ADDITEM_NEW:				new_++;				break;							case ADDITEM_OVERAMOUNT:				return 2;		}				value = sd->inventory_data[idx]->value_buy;//		value = pc->modifybuyvalue(sd, sd->inventory_data[idx]->value_buy);		z+= (double)value*amount;		w+= itemdb_weight(nameid) * amount;	}	if( nd->master_nd ) { // Script-controlled shops		return npc->selllist_sub(sd, n, item_list, nd->master_nd);	}		if( z > (double)sd->status.zeny )		return 1; // Not Enough Zeny	if( w + sd->weight > sd->max_weight )		return 2; // Too heavy	if( pc->inventoryblank(sd) < new_ )		return 3; // Not enough space to store items	pc->payzeny(sd,(int)z,LOG_TYPE_NPC, NULL);	// add items		for( i = 0; i < n; i++ ) {		int amount, idx;		struct item it;		idx    = item_list[i*2]-2;		amount = item_list[i*2+1];		it.nameid = sd->status.inventory[idx].nameid;		it.amount = sd->status.inventory[idx].amount;		it.equip = 0; //Don't equip.		it.identify = sd->status.inventory[idx].identify;		it.refine = sd->status.inventory[idx].refine;		it.attribute = sd->status.inventory[idx].attribute;		it.card[0] = sd->status.inventory[idx].card[0];		it.card[1] = sd->status.inventory[idx].card[1];		it.card[2] = sd->status.inventory[idx].card[2];		it.card[3] = sd->status.inventory[idx].card[3];		it.expire_time = sd->status.inventory[idx].expire_time;		it.bound = sd->status.inventory[idx].bound;		// pc->additem function		pc->additem(sd, &it, amount, LOG_TYPE_NPC);	}	return 0;}

Also, I know I left the ' skill_idx = get_count(MC_DISCOUNT) ' in there even though it's currently not being used, since I set the val to the item's data val. Just didn't feel like removing it yet xD haven't decided whether or not to leave it as an option when attempting to buy back.

Interesting enough though, if I don't specify whether or not the item is bound or has an expire time, the shop will give me a Bound Expiring item. When attempting to Right-Click the item to view it, it'll result in a client crash. O.o perhaps that's an underlying issue in itself.

 

I'll post back with an update regarding my sql attempts @.@; (Hope it works).

 

 

Edit: Okay, I couldn't figure out how to make it work with SQL. Mainly because of what you said before Annieruru regarding WFIFOW(fd,4+c*10)=i+2; calling from inventory. I can't seem to overwrite that data to sql even if I were to manually set the data, the client still tries to pull inventory information. Regarding this, I know that MAX_INVENTORY is 100 ( by default ), I was thinking maybe there is a way to create a hidden 10+ slots that we can set ourselves, and actually give the players items in those slots only and then have the client only pull data from those last 10 slots since it would be inventory space. But, I came to the realization that it would be where you ended up... too many remaining issues, about whether or not a player closes shop, drops/deletes item, sells/buys, stores in cart etc....

 

Edit 2:

However, your @marketclone & Your post here got me thinking. This all maybe possible using vending. If an NPC can have a chat window, then surely it must be able to have a vending window and a cart, the only real issue I can come across with this is, limiting what player can access that vending window.

Edited by GmOcean

Share this post


Link to post
Share on other sites
  • 0

Interesting enough though, if I don't specify whether or not the item is bound or has an expire time, the shop will give me a Bound Expiring item. When attempting to Right-Click the item to view it, it'll result in a client crash. O.o perhaps that's an underlying issue in itself.

athena script engine has always initialized every variable as 0

but in C, if you declare a variable and did not initialized it, it will gives you some random number like 25774833

.

int number; // declare a variableprintf( "%d", number ); // some random numberint number = 0; // declare and initialize a variableprintf( "%d", number ); // return 0int number; // declare a variablenumber = 0; // set the valueprintf( "%d", number ); // return 0
.

nowadays the C compiler should throw you an error on 1st example ...

me ( and probably you ) still using outdated microsoft visual C++ 2010 it doesn't throw error though

 

your client crashed because it has some random high number,

you should reset all values as 0 if you don't want to set every variable in that structure

memset( &it, 0, sizeof(it) );

Share this post


Link to post
Share on other sites
  • 0

Lol, yeah. I'm using C++ 2010 @.@; As for, memset,  I don't actually mind setting all the information myself, since this makes it easier for me to understand lol. I may not have been able to complete this with a successful creation ( set my first src creation too high xD ). But I did learn quite a bit, sadly I kept hitting an unbreakable client wall >.>;

 

Edit:

Okay, so I've decided to go with a small work around that IS working for now.

Again this is the code block I'm currently using:

 

NPC.C

int npc_selllist(struct map_session_data* sd, int n, unsigned short* item_list) {	double z;	int i,w,new_/*, skill_idx = skill->get_index(MC_DISCOUNT)*/; //Currently removing Discount modifier.	struct npc_data *nd;	nullpo_retr(1, sd);	nullpo_retr(1, item_list);	if( ( nd = npc->checknear(sd, map->id2bl(sd->npc_shopid)) ) == NULL ) {		return 1;	}	if( nd->subtype != SHOP ) {		if( !(nd->subtype == SCRIPT && nd->u.scr.shop && nd->u.scr.shop->type == NST_ZENY) )			return 1;	}		z = 0;	w = 0;	new_ = 0;	// verify the sell list	for( i = 0; i < n; i++ ) {		int nameid, amount, idx, value;		idx    = item_list[i*2]-2;		amount = item_list[i*2+1];		if( idx >= MAX_INVENTORY || idx < 0 || amount < 0 ) {			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;		}		switch( pc->checkadditem(sd,nameid,amount) ) {			case ADDITEM_NEW:				new_++;				break;							case ADDITEM_OVERAMOUNT:				return 2;		}				value = sd->inventory_data[idx]->value_buy;/*// Currently Removing Discount Modifier	value = pc->modifybuyvalue(sd, sd->inventory_data[idx]->value_buy);*/		z+= (double)value*amount;		w+= itemdb_weight(nameid) * amount;	}// TODO - Change this to npc->buybacklist_sub to control how it works with a shop attached to an NPC	if( nd->master_nd ) { // Script-controlled shops		return npc->selllist_sub(sd, n, item_list, nd->master_nd);	}		if( z > (double)sd->status.zeny )		return 1; // Not Enough Zeny	if( w + sd->weight > sd->max_weight )		return 2; // Too heavy	if( pc->inventoryblank(sd) < new_ )		return 3; // Not enough space to store items	pc->payzeny(sd,(int)z,LOG_TYPE_NPC, NULL);	// add items		for( i = 0; i < n; i++ ) {		int amount, idx;		struct item it;		idx    = item_list[i*2]-2;		amount = item_list[i*2+1];		it.nameid = sd->status.inventory[idx].nameid;		it.amount = sd->status.inventory[idx].amount;		it.equip = 0; //Don't equip.		it.identify = sd->status.inventory[idx].identify;		it.refine = sd->status.inventory[idx].refine;		it.attribute = sd->status.inventory[idx].attribute;		it.card[0] = sd->status.inventory[idx].card[0];		it.card[1] = sd->status.inventory[idx].card[1];		it.card[2] = sd->status.inventory[idx].card[2];		it.card[3] = sd->status.inventory[idx].card[3];		it.expire_time = 0; //Normally can't even sell rental items.		it.bound = 0; // Normally can't even sell bound items.		// pc->additem function		pc->additem(sd, &it, amount, LOG_TYPE_NPC);	}	return 0;}

CLIF.C

/// Presents list of items, that can be bought back from an NPC shop (ZC_PC_SELL_ITEMLIST)./// 00c7 <packet len>.W { <index>.W <price>.L <overcharge price>.L }*void clif_buybacklist(struct map_session_data *sd){	int fd,i,c=0,val;	nullpo_retv(sd);	fd=sd->fd;	WFIFOHEAD(fd, MAX_INVENTORY * 10 + 4);	WFIFOW(fd,0)=0xc7;	for( i = 0; i < MAX_INVENTORY; i++ )	{		if( sd->status.inventory[i].nameid > 0 && sd->inventory_data[i] && sd->status.inventory[i].expire_time && sd->status.inventory[i].bound )		{			if( !itemdb_cansell(&sd->status.inventory[i], pc_get_group_level(sd)) )				continue;						//This allows the selling(Buyback) of items that have both an expiration time and are bound to the player ONLY. (Normally not possible).						val=sd->inventory_data[i]->value_sell;			if( val < 0 )				continue;			WFIFOW(fd,4+c*10)=i+2;			WFIFOL(fd,6+c*10)=val;			WFIFOL(fd,10+c*10)=pc->modifysellvalue(sd,val); //Can I remove this if my npc.c data doesn't use it?			c++;		}	}	WFIFOW(fd,2)=c*10+4;	WFIFOSET(fd,WFIFOW(fd,2));}

Also CLIF.C

clif->buybacklist = clif_buybacklist;

 

 

 

However, currently I'm stuck at an issue, where the code block in npc.c replaces the original. I've tried creating my own block, but can't find the src where it points to it. For now I'm assuming that clicking the SELL button in a shop will always default to: npc_selllist

 

So I decided to try checking for a variable the player may have set, but I don't think i'm doing it right, here's my code with the changes:

 

/// Player item selling to npc shop.////// @param item_list 'n' pairs <index,amount>/// @return result code for clif->parse_NpcSellListSendint npc_selllist(struct map_session_data* sd, int n, unsigned short* item_list) {	double z;	int i,skill_t, skill_idx = skill->get_index(MC_OVERCHARGE);	struct npc_data *nd;	int w, new_; //BuyBack Feature	char buyback[20]; //BuyBack Feature	nullpo_retr(1, sd);	nullpo_retr(1, item_list);	if( ( nd = npc->checknear(sd, map->id2bl(sd->npc_shopid)) ) == NULL ) {		return 1;	}	if( nd->subtype != SHOP ) {		if( !(nd->subtype == SCRIPT && nd->u.scr.shop && nd->u.scr.shop->type == NST_ZENY) )			return 1;	}	if( pc_readglobalreg( sd, script->add_str( buyback ) ) ) {		w = 0;		new_ = 0;	}	z = 0;	// verify the sell list	for( i = 0; i < n; i++ ) {		int nameid, amount, idx, value;		idx    = item_list[i*2]-2;		amount = item_list[i*2+1];		if( idx >= MAX_INVENTORY || idx < 0 || amount < 0 ) {			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( pc_readglobalreg( sd, script->add_str( buyback ) ) ) { //BuyBack Feature			value = sd->inventory_data[idx]->value_buy; //No Discount when buying back.			w+= itemdb_weight(nameid) * amount;		} else {		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);	}	if( pc_readglobalreg( sd, script->add_str( buyback ) ) ) { //BuyBack Feature		if( z > (double)sd->status.zeny )			return 1; // Not Enough Zeny		if( w + sd->weight > sd->max_weight )			return 2; // Too heavy		if( pc->inventoryblank(sd) < new_ )			return 3; // Not enough space to store items		pc->payzeny(sd,(int)z,LOG_TYPE_NPC, NULL);	// add items			for( i = 0; i < n; i++ ) {			int amount, idx;			struct item it;			idx    = item_list[i*2]-2;			amount = item_list[i*2+1];			it.nameid = sd->status.inventory[idx].nameid;			it.amount = sd->status.inventory[idx].amount;			it.equip = 0; //Don't equip.			it.identify = sd->status.inventory[idx].identify;			it.refine = sd->status.inventory[idx].refine;			it.attribute = sd->status.inventory[idx].attribute;			it.card[0] = sd->status.inventory[idx].card[0];			it.card[1] = sd->status.inventory[idx].card[1];			it.card[2] = sd->status.inventory[idx].card[2];			it.card[3] = sd->status.inventory[idx].card[3];			it.expire_time = 0;			it.bound = sd->status.inventory[idx].bound;			// pc->additem function			pc->additem(sd, &it, amount, LOG_TYPE_NPC);		}	} else {	// delete items	for( i = 0; i < n; i++ ) {		int amount, idx;		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( pet->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, amount, 0, 6, LOG_TYPE_NPC);	}	if( z > MAX_ZENY )		z = MAX_ZENY;	pc->getzeny(sd, (int)z, LOG_TYPE_NPC, NULL);	// custom merchant shop exp bonus	if( battle_config.shop_exp > 0 && z > 0 && ( skill_t = pc->checkskill2(sd,skill_idx) ) > 0) {		if( sd->status.skill[skill_idx].flag >= SKILL_FLAG_REPLACED_LV_0 )			skill_t = sd->status.skill[skill_idx].flag - SKILL_FLAG_REPLACED_LV_0;		if( skill_t > 0 ) {			z = z * (double)skill_t * (double)battle_config.shop_exp/10000.;			if( z < 1 )				z = 1;			pc->gainexp(sd, NULL, 0, (int)z, false);		}	}	}	return 0;}

 

 

Lastly the final problems i've arrived at is this:

1. I don't know how to set the variable to 0 from within the src code. I've tried looking at *set in script.c but I don't think I can apply those methods here.

2. As of right now, I have a script that gives players an item for 5seconds. However, if they don't " buy " those items before they expire, then it'll disappear from the shop window when attempting to interact with it. My guess is this is because it no longer exists in your inventory so the shop says it no longer exists. But I think this can be fixed if I can store that information in a separate array. Just I don't know how to make one in src.

 

3. If #2 isn't fixed with a separate array, then I need to do proper clean up, by increasing the expiration time and deleting the items should they click cancel. So, how do I tell if a player clicked cancel or not? I'd assume it'd be like *close or *close2 but I can't find it at all.

 

Edit 2:

Okay, i've got MOST of this all done now, i'd say I'm about 75-80% completely done with adding this in. I just need to know how to check to see if the player is currently using the shop.

The reason is because, inorder to access the buyback feature, a variable is set to 1 " from an NPC ". Should the player complete the transaction by buying something, the src sets it to 0.

BUT should they click cancel or close the shop from any other means then the variable is set to 1 still. I need to put the NPC on a loop, to check if they are using the NPC or not. Then thusly set to 0 when they aren't.

Edited by GmOcean

Share this post


Link to post
Share on other sites
  • 0

Well, I've managed to make this come a far way, but I'm having a slight issue with SQL atm, and I don't know why it's doing this. Heres the code i'm currently using:

		// Attempt to delete the previous information if it exists, and then insert the new information in its place.		if( SQL->Query( map->mysql_handle, "DELETE FROM buyback WHERE id = %d AND char_id = %d", id, sd->status.char_id ) == SQL_ERROR )			continue;		//Store the Item into a SQL Table for SQL to use. (Need to figure out how to limit to last 10).		if( SQL->Query( map->mysql_handle, "INSERT INTO buyback (id, char_id, nameid, amount, identify, refine, attribute, card1, card2, card3, card4, bound, expire_time) VALUES ( %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d )", 			id, //ID used to limit last sold to 10 items. (Can be modified below).			sd->status.char_id, 			sd->status.inventory[idx].nameid, 			sd->status.inventory[idx].amount, 			sd->status.inventory[idx].identify, 			sd->status.inventory[idx].refine, 			sd->status.inventory[idx].attribute, 			sd->status.inventory[idx].card[1], 			sd->status.inventory[idx].card[2], 			sd->status.inventory[idx].card[3], 			sd->status.inventory[idx].card[4], 			sd->status.inventory[idx].bound, 			sd->status.inventory[idx].expire_time 			) == SQL_ERROR ) { Sql_ShowDebug( map->mysql_handle ); }

You'll see that in the above, I delete a row if it already exists, and then insert new information back in. I do it this way, because it seems that Herc doesn't support " ON DUPLICATE KEY UPDATE X = %d ", atleast all my attempts were fails with that.

 

But the issue I'm having is that it's not updating the information in the correct columns. Namely it SKIPS the char_id column, and adds all the information 1 column over as you'll see below.

id, char_id, nameid, amount, identify, refine, attribute, card1, card2, card3, card4, bound, expire_time'1', '0', '150001', '511', '1', '1', '0', '0', '0', '0', '0', '0', '0'

It's completely beyond my reasoning atm. It was working fine BEFORE I added in the DELETE query, but now it's acting weird... any solutions?

Share this post


Link to post
Share on other sites
  • 0

I found it hard to believe 'on duplicate key update' doesn't work

 

create table test (
char_id int(11) primary key,
name varchar(23)
) engine = innodb;
insert into test select char_id, name from `char`;

.

ACMD(test) {	if ( SQL->Query( map->mysql_handle, "insert into test values (150000,'AnnieRuru0') on duplicate key update name = 'AnnieRuru0'" ) == SQL_SUCCESS )		clif->message( fd, "success" );	else		clif->message( fd, "failed" );	return true;}
ACMD_DEF(test),
.

.

your `buyback` table, drop the `id` field, then set `char_id` as primary,

then maybe set a delay like 2 seconds before the user can use another shop

Share this post


Link to post
Share on other sites
  • 0

Okay, I made a few changes to it and it works, but it seems like whenever a player sells the items to the shop, which is then updated/inserted to the sql table from within the source that individual player lags anywhere from 1-5seconds depending on the amount of items they are selling. On average 100 separate items results in 3-5second delay.

However this delay only applies to the character taking action, everything else will update as normal.

Obviously it's because I'm running 2 queries per item, since update on duplicate key won't work for my particular situation. (it does now indeed work though).

Is there anyway I can have the source code run the sql script as if it were being run in the background, like how npc scripts do?

Edited by GmOcean

Share this post


Link to post
Share on other sites
  • 0

http://rathena.org/board/topic/92497-chatroom-that-retrives-all-the-id-on-it/?hl=hercules#entry244793

 

btw for this part

+	if ( cd->owner->type == BL_PC ) {+		struct npc_data* nd = npc->name2id("OnPCJoinChatEvent");+		mapreg->setreg( script->add_str("$@waitingroomowner"), ((TBL_PC*)cd->owner)->status.account_id );+		script->run( nd->u.scr.script, 0, sd->bl.id, nd->bl.id );+	}
it should be
	if ( cd->owner->type == BL_PC ) {		struct npc_data *nd = npc->name2id("OnPCJoinChatEvent");		if ( !nd ) {			ShowError( "OnPCJoinChatEvent not found !" );			return false;		}		mapreg->setreg( script->add_str("$@waitingroomowner"), ((TBL_PC*)cd->owner)->status.account_id );		script->run( nd->u.scr.script, 0, sd->bl.id, nd->bl.id );	}
otherwise the server just simply crash without giving any sort of error if OnPCJoinChatEvent not exist

Share this post


Link to post
Share on other sites
  • 0

Wait, what? How does the src code for your modified waiting room help me? I understand, that it goes to SRC then SRC tells it to go back to script, which will then run w/e, but i'm trying to eliminate the need of an npc script as much as possible.

 

I was thinking maybe instead of running the Query everytime an item is sold for each and every item, maybe I could just run a smaller query for just the last 10 items.

int npc_selllist(struct map_session_data* sd, int n, unsigned short* item_list) {	double z;	int i,skill_t, skill_idx = skill->get_index(MC_OVERCHARGE);	struct npc_data *nd;	int w, new_; //BuyBack Feature	uint64 last_sold; //BuyBack Feature	nullpo_retr(1, sd);	nullpo_retr(1, item_list);	if( ( nd = npc->checknear(sd, map->id2bl(sd->npc_shopid)) ) == NULL ) {		return 1;	}	if( nd->subtype != SHOP ) {		if( !(nd->subtype == SCRIPT && nd->u.scr.shop && nd->u.scr.shop->type == NST_ZENY) )			return 1;	}	if( pc_readglobalreg( sd, script->add_str( "buyback" ) ) ) {		w = 0;		new_ = 0;	}	z = 0;	// verify the sell list	for( i = 0; i < n; i++ ) {		int nameid, amount, idx, value;		idx    = item_list[i*2]-2;		amount = item_list[i*2+1];		if( idx >= MAX_INVENTORY || idx < 0 || amount < 0 ) {			return 1;		}		nameid = sd->status.inventory[idx].nameid;		if( !nameid || !sd->inventory_data[idx] || sd->status.inventory[idx].amount < amount ) {			return 1;		}		if( !pc_readglobalreg( sd, script->add_str( "buyback" ) ) ) { //Buyback feature doesn't support script-controlled shops.			if( nd->master_nd ) {// Script-controlled shops decide by themselves, what can be sold and at what price.				continue;			}		}		if( pc_readglobalreg( sd, script->add_str( "buyback" )  ) ) { //BuyBack Feature			value = sd->inventory_data[idx]->value_buy; //No Discount when buying back.			w+= itemdb_weight(nameid) * amount;		} else {			value = pc->modifysellvalue(sd, sd->inventory_data[idx]->value_sell);		}		z+= (double)value*amount;	}	if( !pc_readglobalreg( sd, script->add_str( "buyback" )  ) ) { //Only works if NOT BuyBack Feature. (Currently not supported until official works).		if( nd->master_nd ) { // Script-controlled shops			return npc->selllist_sub(sd, n, item_list, nd->master_nd);		}	}	if( pc_readglobalreg( sd, script->add_str( "buyback" ) ) ) { //BuyBack Feature				if( z > (double)sd->status.zeny )			return 1; // Not Enough Zeny		if( w + sd->weight > sd->max_weight )			return 2; // Too heavy		if( pc->inventoryblank(sd) < new_ )			return 3; // Not enough space to store items	pc->payzeny(sd,(int)z,LOG_TYPE_NPC, NULL);		// add items		for( i = 0; i < n; i++ ) {			int amount, idx;			struct item it;			idx    = item_list[i*2]-2;			amount = item_list[i*2+1];			it.nameid = sd->status.inventory[idx].nameid;			it.amount = sd->status.inventory[idx].amount;			it.equip = 0; //Don't equip.			it.identify = sd->status.inventory[idx].identify;			it.refine = sd->status.inventory[idx].refine;			it.attribute = sd->status.inventory[idx].attribute;			it.card[0] = sd->status.inventory[idx].card[0];			it.card[1] = sd->status.inventory[idx].card[1];			it.card[2] = sd->status.inventory[idx].card[2];			it.card[3] = sd->status.inventory[idx].card[3];			it.expire_time = sd->status.inventory[idx].expire_time; // Normally can't sell Rental Items.			it.bound = sd->status.inventory[idx].bound; // Normally can't sell Bound Items.			//Do the below so player has enough inventory space. (Probably not needed since we do the check above).			pc->delitem(sd, idx, amount, 0, 6, LOG_TYPE_NPC); //Delete the Item With Expiration & Bound first.			if( SQL->Query( map->mysql_handle, "DELETE FROM buyback WHERE char_id = %d AND nameid = %d AND amount = %d AND identify = %d AND refine = %d AND attribute = %d AND card1 = %d AND card2 = %d AND card3 = %d AND card4 = %d AND bound = %d AND expire_time = %d",				sd->status.char_id,				sd->status.inventory[idx].nameid,				sd->status.inventory[idx].amount,				sd->status.inventory[idx].identify,				sd->status.inventory[idx].refine,				sd->status.inventory[idx].attribute,				sd->status.inventory[idx].card[0],				sd->status.inventory[idx].card[1],				sd->status.inventory[idx].card[2],				sd->status.inventory[idx].card[3],				sd->status.inventory[idx].bound,				sd->status.inventory[idx].expire_time				) == SQL_ERROR ) { Sql_ShowDebug( map->mysql_handle ); }			it.expire_time = it.bound = 0; //Set Expiration and Bound to 0			pc->additem(sd, &it, amount, LOG_TYPE_NPC); // Add the item to inventory without Expiration and Bound		}	} else {	//Run SQL->Query to obtain number of rows player has, then we adjust id to +1 and then to 1 if above 10.	if( SQL->Query( map->mysql_handle, "SELECT * FROM buyback WHERE char_id = %d", sd->status.char_id ) == SQL_ERROR ) {		Sql_ShowDebug( map->mysql_handle );	}	else { 		last_sold = SQL->NumRows( map->mysql_handle );  //Set ID to number of rows pulled from SQL.	}	// delete items	for( i = 0; i < n; i++ ) {		int amount, idx; 		idx    = item_list[i*2]-2;		amount = item_list[i*2+1];		last_sold++; // +1 From the information gathered above.		if( last_sold > 10 ){ last_sold = 1; } //Reset to 1 to keep buyback items to 10.		if( sd->inventory_data[idx]->type == IT_PETEGG && sd->status.inventory[idx].card[0] == CARD0_PET ) {			if( pet->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]));			}		}		// Attempt to delete the previous information if it exists, and then insert the new information in its place.		if( SQL->Query( map->mysql_handle, "DELETE FROM buyback WHERE char_id = %d AND last_sold = %d", sd->status.char_id, last_sold ) == SQL_ERROR )			continue;		//Store the Item into a SQL Table for SQL to use. (Need to figure out how to limit to last 10).		if( SQL->Query( map->mysql_handle, "INSERT INTO buyback (char_id, nameid, amount, identify, refine, attribute, card1, card2, card3, card4, bound, expire_time, last_sold) VALUES ( %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d )",			sd->status.char_id,			sd->status.inventory[idx].nameid,			sd->status.inventory[idx].amount,			sd->status.inventory[idx].identify,			sd->status.inventory[idx].refine,			sd->status.inventory[idx].attribute,			sd->status.inventory[idx].card[1],			sd->status.inventory[idx].card[2],			sd->status.inventory[idx].card[3],			sd->status.inventory[idx].card[4],			sd->status.inventory[idx].bound,			sd->status.inventory[idx].expire_time,			last_sold			) == SQL_ERROR ) { Sql_ShowDebug( map->mysql_handle ); }		else { pc->delitem(sd, idx, amount, 0, 6, LOG_TYPE_NPC); }	}	if( z > MAX_ZENY )		z = MAX_ZENY;	pc->getzeny(sd, (int)z, LOG_TYPE_NPC, NULL);	if( !pc_readglobalreg( sd, script->add_str( "buyback" ) ) ) { //BuyBack feature doesn't support shop EXP gain of any kind. This is an intended feature.		// custom merchant shop exp bonus		if( battle_config.shop_exp > 0 && z > 0 && ( skill_t = pc->checkskill2(sd,skill_idx) ) > 0) {			if( sd->status.skill[skill_idx].flag >= SKILL_FLAG_REPLACED_LV_0 )				skill_t = sd->status.skill[skill_idx].flag - SKILL_FLAG_REPLACED_LV_0;			if( skill_t > 0 ) {				z = z * (double)skill_t * (double)battle_config.shop_exp/10000.;				if( z < 1 )					z = 1;				pc->gainexp(sd, NULL, 0, (int)z, false);				}			}		}	}	pc->setregistry(sd, script->add_str( "buyback" ), 0); // Set buyback player variable to 0 to reset whether or not they are using a buyback shop	return 0;}

 

That's what I have so far. Don't mind the sql query right before pc->additem, i'm still trying to get that one to work... Can't figure out why it won't work OR a more effective method.

So would it be possible to take the sql query section of this and make it run for only the last 10 items?

for( i = 0; i < n; i++ ) {		int amount, idx; 		idx    = item_list[i*2]-2;		amount = item_list[i*2+1];		last_sold++; // +1 From the information gathered above.		if( last_sold > 10 ){ last_sold = 1; } //Reset to 1 to keep buyback items to 10.		// Attempt to delete the previous information if it exists, and then insert the new information in its place.		if( SQL->Query( map->mysql_handle, "DELETE FROM buyback WHERE char_id = %d AND last_sold = %d", sd->status.char_id, last_sold ) == SQL_ERROR )			continue;		//Store the Item into a SQL Table for SQL to use. (Need to figure out how to limit to last 10).		if( SQL->Query( map->mysql_handle, "INSERT INTO buyback (char_id, nameid, amount, identify, refine, attribute, card1, card2, card3, card4, bound, expire_time, last_sold) VALUES ( %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d )",			sd->status.char_id,			sd->status.inventory[idx].nameid,			sd->status.inventory[idx].amount,			sd->status.inventory[idx].identify,			sd->status.inventory[idx].refine,			sd->status.inventory[idx].attribute,			sd->status.inventory[idx].card[1],			sd->status.inventory[idx].card[2],			sd->status.inventory[idx].card[3],			sd->status.inventory[idx].card[4],			sd->status.inventory[idx].bound,			sd->status.inventory[idx].expire_time,			last_sold			) == SQL_ERROR ) { Sql_ShowDebug( map->mysql_handle ); }		else { continue; }	}

Obviously, I would need to get the final result of the loop run on the delete, but I wouldn't know where that is, because of how the loop is. for( i = 0; i < n; i++ ) i guess I should say, I don't know what the Value of n is. I can't seem to find it anywhere other than it's an int. Does that give it an inherent value of 32758 (or the number thats close to that lol)?

Share this post


Link to post
Share on other sites
  • 0

oh ? I thought you want to run external npc script

many members here has athena script language as their primary scripting language

so its not an optimized method, but most scripters love to do it

maybe I just misunderstood your question

So would it be possible to take the sql query section of this and make it run for only the last 10 items?

like I said, the patch should looks like getinventorylist, and then loop them 1 by 1

 

how about you try to make an npc script as follow

getitem2 1201, 1,1,0,0, 4001,0,0,0;getitem2 1201, 1,1,0,0, 4002,0,0,0;getitem2 1201, 1,1,0,0, 4003,0,0,0;setarray .@card1, 4001, 4002, 4003;for ( .@j = 0; .@j < 3; .@j++ )for ( .@i = 0; .@i < @inventorylist_count; .@i++ )if ( @inventorylist_id == 1201 && @inventorylist_card1 == .@card1[.@j] )...
it should has a 2-dimensional loop

I don't really like the method of just listing last 10 items

I remember the item will auto-sort ... dunno on which condition ... couldn't find it, but certainly when player login

but I don't want take chances of using last 10 index

.

because of how the loop is. for( i = 0; i < n; i++ ) i guess I should say, I don't know what the Value of n is. I can't seem to find it anywhere other than it's an int. Does that give it an inherent value of 32758 (or the number thats close to that lol)?

the n value has declared from the beginning of the function
int npc_selllist(struct map_session_data* sd, int n, unsigned short* item_list) {
int n is some sort like getarg(1)

so try to find which function is actually calling npc->sellist, only 1 result which is clif_parse_NpcSellListSend

inside there has

n = (RFIFOW(fd,2)-4) /4;
another packet stuffs I seriously hate

you know just now I just used 2 hours just to figure out how to write clif_charnameack_pre from @market_clone plugin

this one is quite understandable ... n should be total number of inventory item ?

or maybe means MAX_INVENTORY ?

dunno ... try debug the n value ...

 

I might probably want to spoon feed already, drag on for too long

... provided I have the time

Share this post


Link to post
Share on other sites
  • 0

Okay, this is officially done. Unless of course you see something else that needs to get done or should be changed? At any rate, thank you for all your help Annieruru @.@; I absolutely could not have done this without your help. And definitely not within the time that I did do this in, despite how long it took lmao. Though, since this seems like a rigged patch to make something work, I don't think I'll be making an official src release for it. Perhaps I'll just let the src code sit here D:

 

Especially since there are a few hiccups that need to be fixed. Such as the issues you ran into when you tried a few years ago:

Inventory space, weight, drop item on floor... etc...

 

But these can all be handled through the script side.

Edited by GmOcean

Share this post


Link to post
Share on other sites
  • 0

Okay, this is officially done. Unless of course you see something else that needs to get done or should be changed? At any rate, thank you for all your help Annieruru @.@; I absolutely could not have done this without your help. And definitely not within the time that I did do this in, despite how long it took lmao. Though, since this seems like a rigged patch to make something work, I don't think I'll be making an official src release for it. Perhaps I'll just let the src code sit here D:

 

Especially since there are a few hiccups that need to be fixed. Such as the issues you ran into when you tried a few years ago:

Inventory space, weight, drop item on floor... etc...

 

But these can all be handled through the script side.

 

what does it really do? xD does it allow us to create npcs that vends items with cards and etc? if yes, how can we do it? xD because the sample script doesn't have a list of items or whatsoever xD sorry for being a noob =)

Edited by evilpuncker

Share this post


Link to post
Share on other sites
  • 0

Okay, what it does is this:

1. Any items you sell to an NPC ( A normal NPC doesn't support NPCs used through CallShop ), will get added to a SQL table.

2. When you use CallShop npc_name, 3; it will give those items back to you with an expiration date and bind them to your player.

3. It will then open a Sell window (sadly buy windows don't support detailed items) and then create a list of items based off of those that are both BOUND to your character and have an expiration time. ( Currently not possible unless you use my command ).

4. After that, you just choose the items you want to buy, and click SELL ( yeah lol, I know T.T ), and it'll give you those items back at the cost of the zeny.

5. The items you don't buy, will expire. And the items you did buy will be removed from the SQL table OR updated to show the new amount.

 

Lastly, the items you buy will be a permanent version, unbound and with no expiration time. If you use the sample script and leave the logout_clear, then it will delete all records of that character from the SQL table. ( Most MMO's do this so I like it xD ).

 

Edit: I'll post back with ScreenShots when I get time... For now DINNER !!

Edited by GmOcean

Share this post


Link to post
Share on other sites
  • 0

this the topic I was talking about ...

http://www.eathena.ws/board/index.php?showtopic=270771

 

btw you should use a patch file instead

 

@evilpuncker

basically just make a shop window that sells signed item to the player, using shop interface

 

the trick is, instead of using buy window, this one using sell window with the sell button, but you don't sell the item, but buy it

Share this post


Link to post
Share on other sites
  • 0

Okay, I added the patch file to my first post D: now to dive into my next idea... add overflow zeny from vending directly into Client's integrated bank feature.

Share this post


Link to post
Share on other sites
  • 0

I still don't get it xD mind giving a video or screenies and showing an example of its usage? sorry for being noob xD

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Answer this question...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
Sign in to follow this  

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.