diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/common/mmo.h | 2 | ||||
-rw-r--r-- | src/map/atcommand.c | 27 | ||||
-rw-r--r-- | src/map/clif.c | 224 | ||||
-rw-r--r-- | src/map/clif.h | 8 | ||||
-rw-r--r-- | src/map/map.c | 3 | ||||
-rw-r--r-- | src/map/npc.c | 566 | ||||
-rw-r--r-- | src/map/npc.h | 46 | ||||
-rw-r--r-- | src/map/packets.h | 43 | ||||
-rw-r--r-- | src/map/packets_struct.h | 44 | ||||
-rw-r--r-- | src/map/script.c | 277 | ||||
-rw-r--r-- | src/map/status.c | 324 | ||||
-rw-r--r-- | src/map/status.h | 51 |
12 files changed, 1293 insertions, 322 deletions
diff --git a/src/common/mmo.h b/src/common/mmo.h index b33b01fa7..bd5da6a9f 100644 --- a/src/common/mmo.h +++ b/src/common/mmo.h @@ -48,7 +48,7 @@ // 20120307 - 2012-03-07aRagexeRE+ - 0x970 #ifndef PACKETVER -#define PACKETVER 20120418 + #define PACKETVER 20131223 #endif // PACKETVER #ifndef DISABLE_PACKETVER_RE diff --git a/src/map/atcommand.c b/src/map/atcommand.c index 953f1a0dc..ce73319e2 100644 --- a/src/map/atcommand.c +++ b/src/map/atcommand.c @@ -9273,17 +9273,28 @@ ACMD(searchstore){ return true; } ACMD(costume){ - const char* names[4] = { + const char* names[] = { "Wedding", "Xmas", "Summer", "Hanbok", +#if PACKETVER >= 20131218 + "Oktoberfest", +#endif + }; + const int name2id[] = { + SC_WEDDING, + SC_XMAS, + SC_SUMMER, + SC_HANBOK, +#if PACKETVER >= 20131218 + SC_OKTOBERFEST, +#endif }; - const int name2id[4] = { SC_WEDDING, SC_XMAS, SC_SUMMER, SC_HANBOK }; - unsigned short k = 0; + unsigned short k = 0, len = ARRAYLENGTH(names); if( !message || !*message ) { - for( k = 0; k < 4; k++ ) { + for( k = 0; k < len; k++ ) { if( sd->sc.data[name2id[k]] ) { sprintf(atcmd_output,msg_txt(1473),names[k]);//Costume '%s' removed. clif->message(sd->fd,atcmd_output); @@ -9293,14 +9304,14 @@ ACMD(costume){ } clif->message(sd->fd,msg_txt(1472)); - for( k = 0; k < 4; k++ ) { + for( k = 0; k < len; k++ ) { sprintf(atcmd_output,msg_txt(1471),names[k]);//-- %s clif->message(sd->fd,atcmd_output); } return false; } - for( k = 0; k < 4; k++ ) { + for( k = 0; k < len; k++ ) { if( sd->sc.data[name2id[k]] ) { sprintf(atcmd_output,msg_txt(1470),names[k]);// You're already with a '%s' costume, type '@costume' to remove it. clif->message(sd->fd,atcmd_output); @@ -9308,11 +9319,11 @@ ACMD(costume){ } } - for( k = 0; k < 4; k++ ) { + for( k = 0; k < len; k++ ) { if( strcmpi(message,names[k]) == 0 ) break; } - if( k == 4 ) { + if( k == len ) { sprintf(atcmd_output,msg_txt(1469),message);// '%s' is not a known costume clif->message(sd->fd,atcmd_output); return false; diff --git a/src/map/clif.c b/src/map/clif.c index 6a55ad344..b442d6279 100644 --- a/src/map/clif.c +++ b/src/map/clif.c @@ -61,7 +61,8 @@ static struct packet_itemlist_equip itemlist_equip; static struct packet_storelist_normal storelist_normal; static struct packet_storelist_equip storelist_equip; static struct packet_viewequip_ack viewequip_list; - +static struct packet_npc_market_result_ack npcmarket_result; +static struct packet_npc_market_open npcmarket_open; //#define DUMP_UNKNOWN_PACKET //#define DUMP_INVALID_PACKET @@ -811,7 +812,7 @@ void clif_clearunit_delayed(struct block_list* bl, clr_type type, int64 tick) { void clif_get_weapon_view(struct map_session_data* sd, unsigned short *rhand, unsigned short *lhand) { - if(sd->sc.option&(OPTION_WEDDING|OPTION_XMAS|OPTION_SUMMER|OPTION_HANBOK)) { + if(sd->sc.option&(OPTION_WEDDING|OPTION_XMAS|OPTION_SUMMER|OPTION_HANBOK|OPTION_OKTOBERFEST)) { *rhand = *lhand = 0; return; } @@ -1743,29 +1744,40 @@ void clif_npcbuysell(struct map_session_data* sd, int id) /// Presents list of items, that can be bought in an NPC shop (ZC_PC_PURCHASE_ITEMLIST). /// 00c6 <packet len>.W { <price>.L <discount price>.L <item type>.B <name id>.W }* -void clif_buylist(struct map_session_data *sd, struct npc_data *nd) -{ +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 + nd->u.shop.count * 11); + + WFIFOHEAD(fd, 4 + shop_size * 11); WFIFOW(fd,0) = 0xc6; c = 0; - for( i = 0; i < nd->u.shop.count; i++ ) - { - struct item_data* id = itemdb->exists(nd->u.shop.shop_item[i].nameid); - int val = nd->u.shop.shop_item[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++; + 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; @@ -3170,7 +3182,7 @@ void clif_changelook(struct block_list *bl,int type,int val) case LOOK_BASE: if( !sd ) break; - if( sd->sc.option&(OPTION_WEDDING|OPTION_XMAS|OPTION_SUMMER|OPTION_HANBOK) ) + if( sd->sc.option&(OPTION_WEDDING|OPTION_XMAS|OPTION_SUMMER|OPTION_HANBOK|OPTION_OKTOBERFEST) ) vd->weapon = vd->shield = 0; if( !vd->cloth_color ) @@ -3185,6 +3197,8 @@ void clif_changelook(struct block_list *bl,int type,int val) vd->cloth_color = 0; if( sd->sc.option&OPTION_HANBOK && battle_config.hanbok_ignorepalette ) vd->cloth_color = 0; + if( sd->sc.option&OPTION_OKTOBERFEST /* TODO: config? */ ) + vd->cloth_color = 0; } break; case LOOK_HAIR: @@ -3212,6 +3226,8 @@ void clif_changelook(struct block_list *bl,int type,int val) val = 0; if( sd->sc.option&OPTION_HANBOK && battle_config.hanbok_ignorepalette ) val = 0; + if( sd->sc.option&OPTION_OKTOBERFEST /* TODO: config? */ ) + val = 0; } vd->cloth_color = val; break; @@ -9616,6 +9632,7 @@ void clif_parse_Hotkey(int fd, struct map_session_data *sd) { /// Displays cast-like progress bar (ZC_PROGRESS). /// 02f0 <color>.L <time>.L +/* TODO ZC_PROGRESS_ACTOR <account_id>.L */ void clif_progressbar(struct map_session_data * sd, unsigned int color, unsigned int second) { int fd = sd->fd; @@ -9901,6 +9918,7 @@ void clif_parse_GlobalMessage(int fd, struct map_session_data* sd) WFIFOW(fd,0) = 0x8e; } WFIFOSET(fd, WFIFOW(fd,2)); + #ifdef PCRE_SUPPORT // trigger listening npcs map->foreachinrange(npc_chat->sub, &sd->bl, AREA_SIZE, BL_NPC, text, textlen, &sd->bl); @@ -10058,7 +10076,7 @@ void clif_parse_ActionRequest_sub(struct map_session_data *sd, int action_type, if( pc_cant_act(sd) || sd->sc.option&OPTION_HIDE ) return; - if( sd->sc.option&(OPTION_WEDDING|OPTION_XMAS|OPTION_SUMMER|OPTION_HANBOK) ) + if( sd->sc.option&(OPTION_WEDDING|OPTION_XMAS|OPTION_SUMMER|OPTION_HANBOK|OPTION_OKTOBERFEST) ) return; if( sd->sc.data[SC_BASILICA] || sd->sc.data[SC__SHADOWFORM] ) @@ -11303,7 +11321,7 @@ void clif_parse_UseSkillToId(int fd, struct map_session_data *sd) } } - if( sd->sc.option&(OPTION_WEDDING|OPTION_XMAS|OPTION_SUMMER|OPTION_HANBOK) ) + if( sd->sc.option&(OPTION_WEDDING|OPTION_XMAS|OPTION_SUMMER|OPTION_HANBOK|OPTION_OKTOBERFEST) ) return; if( sd->sc.data[SC_BASILICA] && (skill_id != HP_BASILICA || sd->sc.data[SC_BASILICA]->val4 != sd->bl.id) ) @@ -11395,7 +11413,7 @@ void clif_parse_UseSkillToPosSub(int fd, struct map_session_data *sd, uint16 ski } } - if( sd->sc.option&(OPTION_WEDDING|OPTION_XMAS|OPTION_SUMMER|OPTION_HANBOK) ) + if( sd->sc.option&(OPTION_WEDDING|OPTION_XMAS|OPTION_SUMMER|OPTION_HANBOK|OPTION_OKTOBERFEST) ) return; if( sd->sc.data[SC_BASILICA] && (skill_id != HP_BASILICA || sd->sc.data[SC_BASILICA]->val4 != sd->bl.id) ) @@ -13524,19 +13542,20 @@ void clif_parse_GMRecall2(int fd, struct map_session_data* sd) { /// /item cap_n - capture n monster as pet.(not yet implemented) /// /item agitinvest - reset current global agit investments.(not yet implemented) /// 013f <item/mob name>.24B -void clif_parse_GM_Monster_Item(int fd, struct map_session_data *sd) -{ +/// 09ce <item/mob name>.100B [Ind/Yommy<3] +void clif_parse_GM_Monster_Item(int fd, struct map_session_data *sd) { + struct packet_gm_monster_item *p = P2PTR(fd); int i, count; char *item_monster_name; struct item_data *item_array[10]; struct mob_db *mob_array[10]; - char command[NAME_LENGTH+10]; + char command[256]; - item_monster_name = (char*)RFIFOP(fd,2); - item_monster_name[NAME_LENGTH-1] = '\0'; + item_monster_name = p->str; + item_monster_name[(sizeof(struct packet_gm_monster_item)-2)-1] = '\0'; - if ( (count=itemdb->search_name_array(item_array, 10, item_monster_name, 1)) > 0 ){ - for(i = 0; i < count; i++){ + if ( (count=itemdb->search_name_array(item_array, 10, item_monster_name, 1)) > 0 ) { + for(i = 0; i < count; i++) { if( !item_array[i] ) continue; // It only accepts aegis name @@ -13546,7 +13565,7 @@ void clif_parse_GM_Monster_Item(int fd, struct map_session_data *sd) break; } - if( i < count ){ + if( i < count ) { if( item_array[i]->type == IT_WEAPON || item_array[i]->type == IT_ARMOR ) // nonstackable snprintf(command, sizeof(command)-1, "%citem2 %d 1 0 0 0 0 0 0 0", atcommand->at_symbol, item_array[i]->nameid); else @@ -13562,8 +13581,8 @@ void clif_parse_GM_Monster_Item(int fd, struct map_session_data *sd) return; } - if( (count=mob->db_searchname_array(mob_array, 10, item_monster_name, 1)) > 0){ - for(i = 0; i < count; i++){ + if( (count=mob->db_searchname_array(mob_array, 10, item_monster_name, 1)) > 0) { + for(i = 0; i < count; i++) { if( !mob_array[i] ) continue; // It only accepts sprite name @@ -15402,9 +15421,11 @@ void clif_parse_Auction_buysell(int fd, struct map_session_data* sd) /// List of items offered in a cash shop (ZC_PC_CASH_POINT_ITEMLIST). /// 0287 <packet len>.W <cash point>.L { <sell price>.L <discount price>.L <item type>.B <name id>.W }* /// 0287 <packet len>.W <cash point>.L <kafra point>.L { <sell price>.L <discount price>.L <item type>.B <name id>.W }* (PACKETVER >= 20070711) -void clif_cashshop_show(struct map_session_data *sd, struct npc_data *nd) -{ - int fd,i; +void clif_cashshop_show(struct map_session_data *sd, struct npc_data *nd) { + struct npc_item_list *shop = NULL; + unsigned short shop_size = 0; + int fd,i, c = 0; + int currency[2] = { 0,0 }; #if PACKETVER < 20070711 const int offset = 8; #else @@ -15414,23 +15435,43 @@ void clif_cashshop_show(struct map_session_data *sd, struct npc_data *nd) nullpo_retv(sd); nullpo_retv(nd); + if( nd->subtype == SCRIPT ) { + shop = nd->u.scr.shop->item; + shop_size = nd->u.scr.shop->items; + + npc->trader_count_funds(nd, sd); + + currency[0] = npc->trader_funds[0]; + currency[1] = npc->trader_funds[1]; + } else { + shop = nd->u.shop.shop_item; + shop_size = nd->u.shop.count; + + currency[0] = sd->cashPoints; + currency[1] = sd->kafraPoints; + } + fd = sd->fd; sd->npc_shopid = nd->bl.id; - WFIFOHEAD(fd,offset+nd->u.shop.count*11); + WFIFOHEAD(fd,offset+shop_size*11); WFIFOW(fd,0) = 0x287; - WFIFOW(fd,2) = offset+nd->u.shop.count*11; - WFIFOL(fd,4) = sd->cashPoints; // Cash Points + /* 0x2 = length, set after parsing */ + WFIFOL(fd,4) = currency[0]; // Cash Points #if PACKETVER >= 20070711 - WFIFOL(fd,8) = sd->kafraPoints; // Kafra Points + WFIFOL(fd,8) = currency[1]; // Kafra Points #endif - for( i = 0; i < nd->u.shop.count; i++ ) { - struct item_data* id = itemdb->search(nd->u.shop.shop_item[i].nameid); - WFIFOL(fd,offset+0+i*11) = nd->u.shop.shop_item[i].value; - WFIFOL(fd,offset+4+i*11) = nd->u.shop.shop_item[i].value; // Discount Price - WFIFOB(fd,offset+8+i*11) = itemtype(id->type); - WFIFOW(fd,offset+9+i*11) = ( id->view_id > 0 ) ? id->view_id : id->nameid; + for( i = 0; i < shop_size; i++ ) { + if( shop[i].nameid ) { + struct item_data* id = itemdb->search(shop[i].nameid); + WFIFOL(fd,offset+0+i*11) = shop[i].value; + WFIFOL(fd,offset+4+i*11) = shop[i].value; // Discount Price + WFIFOB(fd,offset+8+i*11) = itemtype(id->type); + WFIFOW(fd,offset+9+i*11) = ( id->view_id > 0 ) ? id->view_id : id->nameid; + c++; + } } + WFIFOW(fd,2) = offset+c*11; WFIFOSET(fd,WFIFOW(fd,2)); } @@ -15449,15 +15490,26 @@ void clif_cashshop_show(struct map_session_data *sd, struct npc_data *nd) /// 7 = You can purchase up to 10 items. /// 8 = Some items could not be purchased. void clif_cashshop_ack(struct map_session_data* sd, int error) { - int fd = sd->fd; + struct npc_data *nd; + int fd = sd->fd; + int currency[2] = { 0,0 }; + + if( (nd = map->id2nd(sd->npc_shopid)) && nd->subtype == SCRIPT ) { + npc->trader_count_funds(nd,sd); + currency[0] = npc->trader_funds[0]; + currency[1] = npc->trader_funds[1]; + } else { + currency[0] = sd->cashPoints; + currency[1] = sd->kafraPoints; + } WFIFOHEAD(fd, packet_len(0x289)); WFIFOW(fd,0) = 0x289; - WFIFOL(fd,2) = sd->cashPoints; + WFIFOL(fd,2) = currency[0]; #if PACKETVER < 20070711 WFIFOW(fd,6) = TOW(error); #else - WFIFOL(fd,6) = sd->kafraPoints; + WFIFOL(fd,6) = currency[1]; WFIFOW(fd,10) = TOW(error); #endif WFIFOSET(fd, packet_len(0x289)); @@ -18048,7 +18100,77 @@ int clif_delay_damage(int64 tick, struct block_list *src, struct block_list *dst return clif->calc_walkdelay(dst,ddelay,type,damage,div); } +/* Thanks to Yommy */ +void clif_parse_NPCShopClosed(int fd, struct map_session_data *sd) { + /* TODO track the state <3~ */ + sd->npc_shopid = 0; +} +/* NPC Market (by Ind after an extensive debugging of the packet, only possible thanks to Yommy <3) */ +void clif_npc_market_open(struct map_session_data *sd, struct npc_data *nd) { + struct npc_item_list *shop = nd->u.scr.shop->item; + unsigned short shop_size = nd->u.scr.shop->items, i, c; + struct item_data *id = NULL; + + npcmarket_open.PacketType = npcmarketopenType; + + for(i = 0, c = 0; i < shop_size; i++) { + if( shop[i].nameid && (id = itemdb->exists(shop[i].nameid)) ) { + npcmarket_open.list[c].nameid = shop[i].nameid; + npcmarket_open.list[c].price = shop[i].value; + npcmarket_open.list[c].qty = shop[i].qty; + npcmarket_open.list[c].type = itemtype(id->type); + npcmarket_open.list[c].view = ( id->view_id > 0 ) ? id->view_id : id->nameid; + c++; + } + } + + npcmarket_open.PacketLength = 4 + ( sizeof(npcmarket_open.list[0]) * c ); + + clif->send(&npcmarket_open,npcmarket_open.PacketLength,&sd->bl,SELF); +} +void clif_parse_NPCMarketClosed(int fd, struct map_session_data *sd) { + /* TODO track the state <3~ */ + sd->npc_shopid = 0; +} +void clif_npc_market_purchase_ack(struct map_session_data *sd, struct packet_npc_market_purchase *req, unsigned char response) { + unsigned short c = 0; + + npcmarket_result.PacketType = npcmarketresultackType; + npcmarket_result.result = response == 0 ? 1 : 0;/* find other values */ + + if( npcmarket_result.result ) { + unsigned short i, list_size = (req->PacketLength - 4) / sizeof(req->list[0]), j; + struct npc_data* nd; + struct npc_item_list *shop = NULL; + unsigned short shop_size = 0; + + nd = map->id2nd(sd->npc_shopid); + shop = nd->u.scr.shop->item; + shop_size = nd->u.scr.shop->items; + + for(i = 0; i < list_size; i++) { + + npcmarket_result.list[i].ITID = req->list[i].ITID; + npcmarket_result.list[i].qty = req->list[i].qty; + + ARR_FIND( 0, shop_size, j, req->list[i].ITID == shop[j].nameid ); + + npcmarket_result.list[i].price = (j != shop_size) ? shop[j].value : 0; + + c++; + } + } + + npcmarket_result.PacketLength = 5 + ( sizeof(npcmarket_result.list[0]) * c );; + + clif->send(&npcmarket_result,npcmarket_result.PacketLength,&sd->bl,SELF); +} +void clif_parse_NPCMarketPurchase(int fd, struct map_session_data *sd) { + struct packet_npc_market_purchase *p = P2PTR(fd); + + clif->npc_market_purchase_ack(sd,p,npc->market_buylist(sd,(p->PacketLength - 4) / sizeof(p->list[0]),p)); +} /* */ unsigned short clif_decrypt_cmd( int cmd, struct map_session_data *sd ) { if( sd ) { @@ -18863,6 +18985,9 @@ void clif_defaults(void) { /* */ clif->delay_damage = clif_delay_damage; clif->delay_damage_sub = clif_delay_damage_sub; + /* NPC Market */ + clif->npc_market_open = clif_npc_market_open; + clif->npc_market_purchase_ack = clif_npc_market_purchase_ack; /*------------------------ *- Parse Incoming Packet *------------------------*/ @@ -19092,4 +19217,9 @@ void clif_defaults(void) { clif->pBankCheck = clif_parse_BankCheck; clif->pBankOpen = clif_parse_BankOpen; clif->pBankClose = clif_parse_BankClose; + /* */ + clif->pNPCShopClosed = clif_parse_NPCShopClosed; + /* NPC Market */ + clif->pNPCMarketClosed = clif_parse_NPCMarketClosed; + clif->pNPCMarketPurchase = clif_parse_NPCMarketPurchase; } diff --git a/src/map/clif.h b/src/map/clif.h index c4088893a..6405bbd3e 100644 --- a/src/map/clif.h +++ b/src/map/clif.h @@ -1034,6 +1034,9 @@ struct clif_interface { /* */ int (*delay_damage) (int64 tick, struct block_list *src, struct block_list *dst, int sdelay, int ddelay, int64 in_damage, short div, unsigned char type); int (*delay_damage_sub) (int tid, int64 tick, int id, intptr_t data); + /* NPC Market */ + void (*npc_market_open) (struct map_session_data *sd, struct npc_data *nd); + void (*npc_market_purchase_ack) (struct map_session_data *sd, struct packet_npc_market_purchase *req, unsigned char response); /*------------------------ *- Parse Incoming Packet *------------------------*/ @@ -1261,6 +1264,11 @@ struct clif_interface { void (*pBankCheck) (int fd, struct map_session_data *sd); void (*pBankOpen) (int fd, struct map_session_data *sd); void (*pBankClose) (int fd, struct map_session_data *sd); + /* */ + void (*pNPCShopClosed) (int fd, struct map_session_data *sd); + /* NPC Market (by Ind after an extensive debugging of the packet, only possible thanks to Yommy <3) */ + void (*pNPCMarketClosed) (int fd, struct map_session_data *sd); + void (*pNPCMarketPurchase) (int fd, struct map_session_data *sd); }; struct clif_interface *clif; diff --git a/src/map/map.c b/src/map/map.c index 7ff4ebcc5..aef34ef00 100644 --- a/src/map/map.c +++ b/src/map/map.c @@ -5630,7 +5630,8 @@ int do_init(int argc, char *argv[]) } npc->event_do_oninit(); // Init npcs (OnInit) - + npc->market_fromsql(); /* after OnInit */ + if (battle_config.pk_mode) ShowNotice("Server is running on '"CL_WHITE"PK Mode"CL_RESET"'.\n"); diff --git a/src/map/npc.c b/src/map/npc.c index 84a2446ad..44df2fe7a 100644 --- a/src/map/npc.c +++ b/src/map/npc.c @@ -56,6 +56,9 @@ struct npc_path_data *npc_last_npd; static struct view_data npc_viewdb[MAX_NPC_CLASS]; static struct view_data npc_viewdb2[MAX_NPC_CLASS2_END-MAX_NPC_CLASS2_START]; +/* for speedup */ +unsigned int npc_market_qty[MAX_INVENTORY]; + static struct script_event_s { //Holds pointers to the commonly executed scripts for speedup. [Skotlex] struct event_data *event[UCHAR_MAX]; @@ -1150,8 +1153,10 @@ int npc_click(struct map_session_data* sd, struct npc_data* nd) } if(!nd) return 1; + if ((nd = npc->checknear(sd,&nd->bl)) == NULL) return 1; + //Hidden/Disabled npc. if (nd->class_ < 0 || nd->option&(OPTION_INVISIBLE|OPTION_HIDE)) return 1; @@ -1164,7 +1169,11 @@ int npc_click(struct map_session_data* sd, struct npc_data* nd) clif->cashshop_show(sd,nd); break; case SCRIPT: - script->run(nd->u.scr.script,0,sd->bl.id,nd->bl.id); + if( nd->u.scr.shop && nd->u.scr.shop->items && nd->u.scr.trader ) { + if( !npc->trader_open(sd,nd) ) + return 1; + } else + script->run(nd->u.scr.script,0,sd->bl.id,nd->bl.id); break; case TOMB: npc->run_tomb(sd,nd); @@ -1231,16 +1240,22 @@ int npc_buysellsel(struct map_session_data* sd, int id, int type) { if ((nd = npc->checknear(sd,map->id2bl(id))) == NULL) return 1; - if (nd->subtype!=SHOP) { - ShowError("no such shop npc : %d\n",id); + 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; + sd->npc_id = 0; return 1; } - if (nd->option & OPTION_INVISIBLE) // can't buy if npc is not visible (hack?) + + 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 + + if( nd->class_ < 0 && !sd->state.callshop ) {// not called through a script and is not a visible NPC so an invalid call return 1; } @@ -1253,6 +1268,7 @@ int npc_buysellsel(struct map_session_data* sd, int id, int type) { } else { clif->selllist(sd); } + return 0; } @@ -1261,14 +1277,33 @@ int npc_buysellsel(struct map_session_data* sd, int id, int type) { *------------------------------------------*/ int npc_cashshop_buylist(struct map_session_data *sd, int points, int count, unsigned short* item_list) { int i, j, nameid, amount, new_, w, vt; - struct npc_data *nd = (struct npc_data *)map->id2bl(sd->npc_shopid); - - if( !nd || nd->subtype != CASHSHOP ) - return 1; + struct npc_data *nd = NULL; + struct npc_item_list *shop = NULL; + unsigned short shop_size = 0; if( sd->state.trading ) return 4; + if( count <= 0 ) + return 5; + + if( points < 0 ) + return 6; + + if( !(nd = (struct npc_data *)map->id2bl(sd->npc_shopid)) ) + return 1; + + if( nd->subtype != CASHSHOP ) { + if( nd->subtype == SCRIPT && nd->u.scr.shop && nd->u.scr.shop->type != NST_ZENY && nd->u.scr.shop->type != NST_MARKET ) { + shop = nd->u.scr.shop->item; + shop_size = nd->u.scr.shop->items; + } else + return 1; + } else { + shop = nd->u.shop.shop_item; + shop_size = nd->u.shop.count; + } + new_ = 0; w = 0; vt = 0; // Global Value @@ -1281,12 +1316,13 @@ int npc_cashshop_buylist(struct map_session_data *sd, int points, int count, uns if( !itemdb->exists(nameid) || amount <= 0 ) return 5; - ARR_FIND(0,nd->u.shop.count,j,nd->u.shop.shop_item[j].nameid == nameid); - if( j == nd->u.shop.count || nd->u.shop.shop_item[j].value <= 0 ) + ARR_FIND(0,shop_size,j,shop[j].nameid == nameid); + if( j == shop_size || shop[j].value <= 0 ) return 5; if( !itemdb->isstackable(nameid) && amount > 1 ) { - ShowWarning("Player %s (%d:%d) sent a hexed packet trying to buy %d of nonstackable item %d!\n", sd->status.name, sd->status.account_id, sd->status.char_id, amount, nameid); + ShowWarning("Player %s (%d:%d) sent a hexed packet trying to buy %d of nonstackable item %d!\n", + sd->status.name, sd->status.account_id, sd->status.char_id, amount, nameid); amount = item_list[i*2+0] = 1; } @@ -1298,21 +1334,27 @@ int npc_cashshop_buylist(struct map_session_data *sd, int points, int count, uns return 3; } - vt += nd->u.shop.shop_item[j].value * amount; + vt += shop[j].value * amount; w += itemdb_weight(nameid) * amount; } if( w + sd->weight > sd->max_weight ) return 3; + if( pc->inventoryblank(sd) < new_ ) return 3; + if( points > vt ) points = vt; // Payment Process ---------------------------------------------------- - if( sd->kafraPoints < points || sd->cashPoints < (vt - points) ) - return 6; - pc->paycash(sd,vt,points); - + if( nd->subtype == SCRIPT && nd->u.scr.shop->type == NST_CUSTOM ) { + if( !npc->trader_pay(nd,sd,vt,points) ) + return 6; + } else { + if( sd->kafraPoints < points || sd->cashPoints < (vt - points) ) + return 6; + pc->paycash(sd,vt,points); + } // Delivery Process ---------------------------------------------------- for( i = 0; i < count; i++ ) { struct item item_tmp; @@ -1356,45 +1398,257 @@ int npc_buylist_sub(struct map_session_data* sd, int n, unsigned short* item_lis return 0; } +/** + * Loads persistent NPC Market Data from SQL + **/ +void npc_market_fromsql(void) { + SqlStmt* stmt = SQL->StmtMalloc(map->mysql_handle); + char name[NAME_LENGTH+1]; + int itemid; + int amount; + + /* TODO inter-server.conf npc_market_data */ + if ( SQL_ERROR == SQL->StmtPrepare(stmt, "SELECT `name`, `itemid`, `amount` FROM `npc_market_data`") + || SQL_ERROR == SQL->StmtExecute(stmt) + ) { + SqlStmt_ShowDebug(stmt); + SQL->StmtFree(stmt); + return; + } + + SQL->StmtBindColumn(stmt, 0, SQLDT_STRING, &name[0], sizeof(name), NULL, NULL); + SQL->StmtBindColumn(stmt, 1, SQLDT_INT, &itemid, 0, NULL, NULL); + SQL->StmtBindColumn(stmt, 2, SQLDT_INT, &amount, 0, NULL, NULL); + + while ( SQL_SUCCESS == SQL->StmtNextRow(stmt) ) { + struct npc_data *nd = NULL; + unsigned short i; + + if( !(nd = npc->name2id(name)) ) { + ShowError("npc_market_fromsql: NPC '%s' not found! skipping...\n",name); + continue; + } else if ( nd->subtype != SCRIPT || !nd->u.scr.shop || !nd->u.scr.shop->items || nd->u.scr.shop->type != NST_MARKET ) { + ShowError("npc_market_fromsql: NPC '%s' is not proper for market, skipping...\n",name); + continue; + } + + for(i = 0; i < nd->u.scr.shop->items; i++) { + if( nd->u.scr.shop->item[i].nameid == itemid ) { + nd->u.scr.shop->item[i].qty = amount; + break; + } + } + + if( i == nd->u.scr.shop->items ) { + ShowError("npc_market_fromsql: NPC '%s' does not sell item %d (qty %d), deleting...\n",name,itemid,amount); + /* TODO inter-server.conf npc_market_data */ + if( SQL_ERROR == SQL->Query(map->mysql_handle, "DELETE FROM `npc_market_data` WHERE `name`='%s' AND `itemid`='%d' LIMIT 1", name,itemid) ) + Sql_ShowDebug(map->mysql_handle); + continue; + } + + } + + SQL->StmtFree(stmt); +} +/** + * Saves persistent NPC Market Data into SQL + **/ +void npc_market_tosql(struct npc_data *nd, unsigned short index) { + /* TODO inter-server.conf npc_market_data */ + if( SQL_ERROR == SQL->Query(map->mysql_handle, "REPLACE INTO `npc_market_data` VALUES ('%s','%d','%d')", nd->exname, nd->u.scr.shop->item[index].nameid, nd->u.scr.shop->item[index].qty) ) + Sql_ShowDebug(map->mysql_handle); +} +/** + * Removes persistent NPC Market Data from SQL + **/ +void npc_market_delfromsql(struct npc_data *nd, unsigned short index) { + /* TODO inter-server.conf npc_market_data */ + if( index == USHRT_MAX ) { + if( SQL_ERROR == SQL->Query(map->mysql_handle, "DELETE FROM `npc_market_data` WHERE `name`='%s'", nd->exname) ) + Sql_ShowDebug(map->mysql_handle); + } else { + if( SQL_ERROR == SQL->Query(map->mysql_handle, "DELETE FROM `npc_market_data` WHERE `name`='%s' AND `itemid`='%d' LIMIT 1", nd->exname, nd->u.scr.shop->item[index].nameid) ) + Sql_ShowDebug(map->mysql_handle); + } +} +/** + * Judges whether to allow and spawn a trader's window. + **/ +bool npc_trader_open(struct map_session_data *sd, struct npc_data *nd) { + + if( !nd->u.scr.shop || !nd->u.scr.shop->items ) + return false; + + switch( nd->u.scr.shop->type ) { + case NST_ZENY: + sd->state.callshop = 1; + npc->buysellsel(sd,nd->bl.id,0); + break; + case NST_MARKET: { + unsigned short i; + + for(i = 0; i < nd->u.scr.shop->items; i++) { + if( nd->u.scr.shop->item[i].qty ) + break; + } + + /* nothing to display, no items available */ + if( i == nd->u.scr.shop->items ) { + clif->colormes(sd->fd,COLOR_RED,"Shop is out of stock! Come again later!");/* TODO messages.conf-it */ + return false; + } + clif->npc_market_open(sd,nd); + } + break; + default: + clif->cashshop_show(sd,nd); + break; + } + + sd->npc_shopid = nd->bl.id; + + return true; +} +/** + * Creates (npc_data)->u.scr.shop and updates all duplicates across the server to match the created pointer + * + * @param master id of the original npc + **/ +void npc_trader_update(int master) { + DBIterator* iter; + struct block_list* bl; + struct npc_data *master_nd = map->id2nd(master); + + CREATE(master_nd->u.scr.shop,struct npc_shop_data,1); + + iter = db_iterator(map->id_db); + + for( bl = (struct block_list*)dbi_first(iter); dbi_exists(iter); bl = (struct block_list*)dbi_next(iter) ) { + if( bl->type == BL_NPC ) { + struct npc_data* nd = (struct npc_data*)bl; + + if( nd->src_id == master ) { + nd->u.scr.shop = master_nd->u.scr.shop; + } + } + } + + dbi_destroy(iter); +} +/** + * Tries to issue a CountFunds event to the shop. + * + * @param nd shop + * @param sd player + **/ +void npc_trader_count_funds(struct npc_data *nd, struct map_session_data *sd) { + char evname[EVENT_NAME_LENGTH]; + struct event_data *ev = NULL; + + npc->trader_funds[0] = npc->trader_funds[1] = 0;/* clear */ + + switch( nd->u.scr.shop->type ) { + case NST_CASH: + npc->trader_funds[0] = sd->cashPoints; + npc->trader_funds[1] = sd->kafraPoints; + return; + case NST_CUSTOM: + break; + default: + ShowError("npc_trader_count_funds: unsupported shop type %d\n",nd->u.scr.shop->type); + return; + } + + snprintf(evname, EVENT_NAME_LENGTH, "%s::OnCountFunds",nd->exname); + + if ( (ev = strdb_get(npc->ev_db, evname)) ) + script->run(ev->nd->u.scr.script, ev->pos, sd->bl.id, ev->nd->bl.id); + else + ShowError("npc_trader_count_funds: '%s' event '%s' not found, operation failed\n",nd->exname,evname); + + /* the callee will rely on npc->trader_funds, upon success script->run updates them */ +} +/** + * Tries to issue a payment to the NPC Event capable of handling it + * + * @param nd shop + * @param sd player + * @param price total cost + * @param points the amount input in the shop by the user to use from the secondary currency (if any is being employed) + * + * @return bool whether it was successful (if the script does not respond it will fail) + **/ +bool npc_trader_pay(struct npc_data *nd, struct map_session_data *sd, int price, int points) { + char evname[EVENT_NAME_LENGTH]; + struct event_data *ev = NULL; + + npc->trader_ok = false;/* clear */ + + snprintf(evname, EVENT_NAME_LENGTH, "%s::OnPayFunds",nd->exname); + + if ( (ev = strdb_get(npc->ev_db, evname)) ) { + pc->setreg(sd,script->add_str("@price"),price); + pc->setreg(sd,script->add_str("@points"),points); + + script->run(ev->nd->u.scr.script, ev->pos, sd->bl.id, ev->nd->bl.id); + } else + ShowError("npc_trader_pay: '%s' event '%s' not found, operation failed\n",nd->exname,evname); + + return npc->trader_ok;/* run script will deal with it */ +} /*========================================== * Cash Shop Buy *------------------------------------------*/ int npc_cashshop_buy(struct map_session_data *sd, int nameid, int amount, int points) { - struct npc_data *nd = (struct npc_data *)map->id2bl(sd->npc_shopid); + struct npc_data *nd = NULL; struct item_data *item; + struct npc_item_list *shop = NULL; int i, price, w; - + unsigned short shop_size = 0; + if( amount <= 0 ) return 5; if( points < 0 ) return 6; - if( !nd || nd->subtype != CASHSHOP ) - return 1; - if( sd->state.trading ) return 4; + + if( !(nd = (struct npc_data *)map->id2bl(sd->npc_shopid)) ) + return 1; if( (item = itemdb->exists(nameid)) == NULL ) return 5; // Invalid Item - ARR_FIND(0, nd->u.shop.count, i, nd->u.shop.shop_item[i].nameid == nameid); - if( i == nd->u.shop.count ) + if( nd->subtype != CASHSHOP ) { + if( nd->subtype == SCRIPT && nd->u.scr.shop && nd->u.scr.shop->type != NST_ZENY && nd->u.scr.shop->type != NST_MARKET ) { + shop = nd->u.scr.shop->item; + shop_size = nd->u.scr.shop->items; + } else + return 1; + } else { + shop = nd->u.shop.shop_item; + shop_size = nd->u.shop.count; + } + + ARR_FIND(0, shop_size, i, shop[i].nameid == nameid); + + if( i == shop_size ) return 5; - if( nd->u.shop.shop_item[i].value <= 0 ) + + if( shop[i].value <= 0 ) return 5; - if(!itemdb->isstackable(nameid) && amount > 1) - { + if(!itemdb->isstackable(nameid) && amount > 1) { ShowWarning("Player %s (%d:%d) sent a hexed packet trying to buy %d of nonstackable item %d!\n", sd->status.name, sd->status.account_id, sd->status.char_id, amount, nameid); amount = 1; } - switch( pc->checkadditem(sd, nameid, amount) ) - { + switch( pc->checkadditem(sd, nameid, amount) ) { case ADDITEM_NEW: if( pc->inventoryblank(sd) == 0 ) return 3; @@ -1407,26 +1661,31 @@ int npc_cashshop_buy(struct map_session_data *sd, int nameid, int amount, int po if( w + sd->weight > sd->max_weight ) return 3; - if( (double)nd->u.shop.shop_item[i].value * amount > INT_MAX ) { + if( (double)shop[i].value * amount > INT_MAX ) { ShowWarning("npc_cashshop_buy: Item '%s' (%d) price overflow attempt!\n", item->name, nameid); ShowDebug("(NPC:'%s' (%s,%d,%d), player:'%s' (%d/%d), value:%d, amount:%d)\n", nd->exname, map->list[nd->bl.m].name, nd->bl.x, nd->bl.y, sd->status.name, sd->status.account_id, sd->status.char_id, - nd->u.shop.shop_item[i].value, amount); + shop[i].value, amount); return 5; } - price = nd->u.shop.shop_item[i].value * amount; + price = shop[i].value * amount; + if( points > price ) points = price; - if( (sd->kafraPoints < points) || (sd->cashPoints < price - points) ) - return 6; - - pc->paycash(sd, price, points); + if( nd->subtype == SCRIPT && nd->u.scr.shop->type == NST_CUSTOM ) { + if( !npc->trader_pay(nd,sd,price,points) ) + return 6; + } else { + if( (sd->kafraPoints < points) || (sd->cashPoints < price - points) ) + return 6; + + pc->paycash(sd, price, points); + } - if( !pet->create_egg(sd, nameid) ) - { + if( !pet->create_egg(sd, nameid) ) { struct item item_tmp; memset(&item_tmp, 0, sizeof(struct item)); item_tmp.nameid = nameid; @@ -1444,104 +1703,115 @@ int npc_cashshop_buy(struct map_session_data *sd, int nameid, int amount, int po /// @return result code for clif->parse_NpcBuyListSend int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list) { struct npc_data* nd; + struct npc_item_list *shop = NULL; double z; int i,j,w,skill_t,new_, idx = skill->get_index(MC_DISCOUNT); - + unsigned short shop_size = 0; + nullpo_retr(3, sd); nullpo_retr(3, item_list); - + nd = npc->checknear(sd,map->id2bl(sd->npc_shopid)); if( nd == NULL ) return 3; - if( nd->subtype != SHOP ) - return 3; - + + if( nd->subtype != SHOP ) { + if( nd->subtype == SCRIPT && nd->u.scr.shop && nd->u.scr.shop->type == NST_ZENY ) { + shop = nd->u.scr.shop->item; + shop_size = nd->u.scr.shop->items; + } else + return 3; + } else { + shop = nd->u.shop.shop_item; + shop_size = nd->u.shop.count; + } + z = 0; w = 0; new_ = 0; // process entries in buy list, one by one for( i = 0; i < n; ++i ) { int nameid, amount, value; - + // find this entry in the shop's sell list - ARR_FIND( 0, nd->u.shop.count, j, - item_list[i*2+1] == nd->u.shop.shop_item[j].nameid || //Normal items - item_list[i*2+1] == itemdb_viewid(nd->u.shop.shop_item[j].nameid) //item_avail replacement - ); - - if( j == nd->u.shop.count ) + ARR_FIND( 0, shop_size, j, + item_list[i*2+1] == shop[j].nameid || //Normal items + item_list[i*2+1] == itemdb_viewid(shop[j].nameid) //item_avail replacement + ); + + if( j == shop_size ) return 3; // no such item in shop - + amount = item_list[i*2+0]; - nameid = item_list[i*2+1] = nd->u.shop.shop_item[j].nameid; //item_avail replacement - value = nd->u.shop.shop_item[j].value; - + nameid = item_list[i*2+1] = shop[j].nameid; //item_avail replacement + value = shop[j].value; + if( !itemdb->exists(nameid) ) return 3; // item no longer in itemdb - + if( !itemdb->isstackable(nameid) && amount > 1 ) { //Exploit? You can't buy more than 1 of equipment types o.O ShowWarning("Player %s (%d:%d) sent a hexed packet trying to buy %d of nonstackable item %d!\n", - sd->status.name, sd->status.account_id, sd->status.char_id, amount, nameid); + sd->status.name, sd->status.account_id, sd->status.char_id, amount, nameid); amount = item_list[i*2+0] = 1; } - + if( nd->master_nd ) { // Script-controlled shops decide by themselves, what can be bought and for what price. continue; } - + switch( pc->checkadditem(sd,nameid,amount) ) { case ADDITEM_EXIST: break; - + case ADDITEM_NEW: new_++; break; - + case ADDITEM_OVERAMOUNT: return 2; } - + value = pc->modifybuyvalue(sd,value); - + z += (double)value * amount; w += itemdb_weight(nameid) * amount; } - + if( nd->master_nd != NULL ) //Script-based shops. return npc->buylist_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); - + for( i = 0; i < n; ++i ) { int nameid = item_list[i*2+1]; int amount = item_list[i*2+0]; struct item item_tmp; - + if (itemdb_type(nameid) == IT_PETEGG) pet->create_egg(sd, nameid); else { memset(&item_tmp,0,sizeof(item_tmp)); item_tmp.nameid = nameid; item_tmp.identify = 1; - + pc->additem(sd,&item_tmp,amount,LOG_TYPE_NPC); } } - + // custom merchant shop exp bonus if( battle_config.shop_exp > 0 && z > 0 && (skill_t = pc->checkskill2(sd,idx)) > 0 ) { if( sd->status.skill[idx].flag >= SKILL_FLAG_REPLACED_LV_0 ) skill_t = sd->status.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 ) @@ -1549,10 +1819,120 @@ int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list) { pc->gainexp(sd,NULL,0,(int)z, false); } } - + return 0; } +/** + * parses incoming npc market purchase list + **/ +int npc_market_buylist(struct map_session_data* sd, unsigned short list_size, struct packet_npc_market_purchase *p) { + struct npc_data* nd; + struct npc_item_list *shop = NULL; + double z; + int i,j,w,new_; + unsigned short shop_size = 0; + + nullpo_retr(1, sd); + nullpo_retr(1, p); + + nd = npc->checknear(sd,map->id2bl(sd->npc_shopid)); + + if( nd == NULL || nd->subtype != SCRIPT || !list_size || !nd->u.scr.shop || nd->u.scr.shop->type != NST_MARKET ) + return 1; + + shop = nd->u.scr.shop->item; + shop_size = nd->u.scr.shop->items; + + z = 0; + w = 0; + new_ = 0; + + // process entries in buy list, one by one + for( i = 0; i < list_size; ++i ) { + int nameid, amount, value; + + // find this entry in the shop's sell list + ARR_FIND( 0, shop_size, j, + p->list[i].ITID == shop[j].nameid || //Normal items + p->list[i].ITID == itemdb_viewid(shop[j].nameid) //item_avail replacement + ); + + if( j == shop_size ) /* TODO find official response for this */ + return 1; // no such item in shop + + if( p->list[i].qty > shop[j].qty ) + return 1; + + amount = p->list[i].qty; + nameid = p->list[i].ITID = shop[j].nameid; //item_avail replacement + value = shop[j].value; + npc_market_qty[i] = j; + + if( !itemdb->exists(nameid) ) /* TODO find official response for this */ + return 1; // item no longer in itemdb + + if( !itemdb->isstackable(nameid) && amount > 1 ) { + //Exploit? You can't buy more than 1 of equipment types o.O + ShowWarning("Player %s (%d:%d) sent a hexed packet trying to buy %d of nonstackable item %d!\n", + sd->status.name, sd->status.account_id, sd->status.char_id, amount, nameid); + amount = p->list[i].qty = 1; + } + + switch( pc->checkadditem(sd,nameid,amount) ) { + case ADDITEM_EXIST: + break; + + case ADDITEM_NEW: + new_++; + break; + + case ADDITEM_OVERAMOUNT: /* TODO find official response for this */ + return 1; + } + + z += (double)value * amount; + w += itemdb_weight(nameid) * amount; + } + + if( z > (double)sd->status.zeny ) /* TODO find official response for this */ + return 1; // Not enough Zeny + + if( w + sd->weight > sd->max_weight ) /* TODO find official response for this */ + return 1; // Too heavy + + if( pc->inventoryblank(sd) < new_ ) /* TODO find official response for this */ + return 1; // Not enough space to store items + + pc->payzeny(sd,(int)z,LOG_TYPE_NPC, NULL); + + for( i = 0; i < list_size; ++i ) { + int nameid = p->list[i].ITID; + int amount = p->list[i].qty; + struct item item_tmp; + + j = npc_market_qty[i]; + + if( p->list[i].qty > shop[j].qty ) /* wohoo someone tampered with the packet. */ + return 1; + + shop[j].qty -= amount; + + npc->market_tosql(nd,j); + + if (itemdb_type(nameid) == IT_PETEGG) + pet->create_egg(sd, nameid); + else { + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid = nameid; + item_tmp.identify = 1; + + pc->additem(sd,&item_tmp,amount,LOG_TYPE_NPC); + } + } + + return 0; +} /// npc_selllist for script-controlled shops int npc_selllist_sub(struct map_session_data* sd, int n, unsigned short* item_list, struct npc_data* nd) @@ -1844,6 +2224,11 @@ int npc_unload(struct npc_data* nd, bool single) { nd->u.scr.label_list = NULL; nd->u.scr.label_list_num = 0; } + if(nd->u.scr.shop) { + if(nd->u.scr.shop->item) + aFree(nd->u.scr.shop->item); + aFree(nd->u.scr.shop); + } } if( nd->u.scr.guild_id ) guild->flag_remove(nd); @@ -2395,7 +2780,7 @@ const char* npc_skip_script(const char* start, const char* buffer, const char* f /// -%TAB%script%TAB%<NPC Name>%TAB%-1,{<code>} /// <map name>,<x>,<y>,<facing>%TAB%script%TAB%<NPC Name>%TAB%<sprite id>,{<code>} /// <map name>,<x>,<y>,<facing>%TAB%script%TAB%<NPC Name>%TAB%<sprite id>,<triggerX>,<triggerY>,{<code>} -const char* npc_parse_script(char* w1, char* w2, char* w3, char* w4, const char* start, const char* buffer, const char* filepath, bool runOnInit) { +const char* npc_parse_script(char* w1, char* w2, char* w3, char* w4, const char* start, const char* buffer, const char* filepath, int options) { int x, y, dir = 0, m, xs = 0, ys = 0; // [Valaris] thanks to fov char mapname[32]; struct script_code *scriptroot; @@ -2466,7 +2851,10 @@ const char* npc_parse_script(char* w1, char* w2, char* w3, char* w4, const char* nd->u.scr.script = scriptroot; nd->u.scr.label_list = label_list; nd->u.scr.label_list_num = label_list_num; - + if( options&NPO_TRADER ) + nd->u.scr.trader = true; + nd->u.scr.shop = NULL; + ++npc_script; nd->bl.type = BL_NPC; nd->subtype = SCRIPT; @@ -2500,7 +2888,7 @@ const char* npc_parse_script(char* w1, char* w2, char* w3, char* w4, const char* nd->u.scr.timerid = INVALID_TIMER; - if( runOnInit ) { + if( options&NPO_ONINIT ) { char evname[EVENT_NAME_LENGTH]; struct event_data *ev; @@ -2604,6 +2992,8 @@ const char* npc_parse_duplicate(char* w1, char* w2, char* w3, char* w4, const ch nd->u.scr.script = dnd->u.scr.script; nd->u.scr.label_list = dnd->u.scr.label_list; nd->u.scr.label_list_num = dnd->u.scr.label_list_num; + nd->u.scr.shop = dnd->u.scr.shop; + nd->u.scr.trader = dnd->u.scr.trader; break; case SHOP: @@ -3656,9 +4046,12 @@ int npc_parsesrcfile(const char* filepath, bool runOnInit) { #ifdef ENABLE_CASE_CHECK if( strcasecmp(w1, "function") == 0 ) DeprecationWarning("npc_parsesrcfile", w1, "function", filepath, strline(buffer, p-buffer)); // TODO #endif // ENABLE_CASE_CHECK - p = npc->parse_script(w1,w2,w3,w4, p, buffer, filepath,runOnInit); + p = npc->parse_script(w1,w2,w3,w4, p, buffer, filepath,runOnInit?NPO_ONINIT:NPO_NONE); } } + else if( strcmp(w2,"trader") == 0 && count > 3 ) { + p = npc->parse_script(w1,w2,w3,w4, p, buffer, filepath,(runOnInit?NPO_ONINIT:NPO_NONE)|NPO_TRADER); + } else if( (i=0, sscanf(w2,"duplicate%n",&i), (i > 0 && w2[i] == '(')) && count > 3 ) { p = npc->parse_duplicate(w1,w2,w3,w4, p, buffer, filepath); @@ -3678,6 +4071,7 @@ int npc_parsesrcfile(const char* filepath, bool runOnInit) { else if( strcasecmp(w2,"shop") == 0 ) { DeprecationWarning("npc_parsesrcfile", w2, "shop", filepath, strline(buffer, p-buffer)); } // TODO else if( strcasecmp(w2,"cashshop") == 0 ) { DeprecationWarning("npc_parsesrcfile", w2, "cashshop", filepath, strline(buffer, p-buffer)); } // TODO else if( strcasecmp(w2, "script") == 0 ) { DeprecationWarning("npc_parsesrcfile", w2, "script", filepath, strline(buffer, p-buffer)); } // TODO + else if( strcasecmp(w2,"trader") == 0 ) DeprecationWarning("npc_parsesrcfile", w2, "trader", filepath, strline(buffer, p-buffer)) // TODO else if( strncasecmp(w2, "duplicate", 9) == 0 ) { char temp[10]; safestrncpy(temp, w2, 10); @@ -3871,7 +4265,7 @@ int npc_reload(void) { "\t-'"CL_WHITE"%d"CL_RESET"' Mobs Cached\n" "\t-'"CL_WHITE"%d"CL_RESET"' Mobs Not Cached\n", npc_id - npc_new_min, npc_warp, npc_shop, npc_script, npc_mob, npc_cache_mob, npc_delay_mob); - + itemdb->name_constants(); instance->reload(); @@ -3890,6 +4284,8 @@ int npc_reload(void) { //Execute the OnInit event for freshly loaded npcs. [Skotlex] ShowStatus("Event '"CL_WHITE"OnInit"CL_RESET"' executed with '"CL_WHITE"%d"CL_RESET"' NPCs.\n",npc->event_doall("OnInit")); + npc->market_fromsql();/* after OnInit */ + // Execute rest of the startup events if connected to char-server. [Lance] if(!intif->CheckForCharServer()){ ShowStatus("Event '"CL_WHITE"OnInterIfInit"CL_RESET"' executed with '"CL_WHITE"%d"CL_RESET"' NPCs.\n", npc->event_doall("OnInterIfInit")); @@ -4024,7 +4420,7 @@ int do_init_npc(bool minimal) { "\t-'"CL_WHITE"%d"CL_RESET"' Mobs Not Cached\n", npc_id - START_NPC_NUM, npc_warp, npc_shop, npc_script, npc_mob, npc_cache_mob, npc_delay_mob); } - + itemdb->name_constants(); if (!minimal) { @@ -4076,6 +4472,9 @@ void npc_defaults(void) { npc->fake_nd = NULL; npc->src_files = NULL; /* */ + npc->trader_ok = false; + npc->trader_funds[0] = npc->trader_funds[1] = 0; + /* */ npc->init = do_init_npc; npc->final = do_final_npc; /* */ @@ -4165,4 +4564,13 @@ void npc_defaults(void) { npc->debug_warps_sub = npc_debug_warps_sub; npc->debug_warps = npc_debug_warps; npc->secure_timeout_timer = npc_rr_secure_timeout_timer; + /* */ + npc->trader_count_funds = npc_trader_count_funds; + npc->trader_pay = npc_trader_pay; + npc->trader_update = npc_trader_update; + npc->market_buylist = npc_market_buylist; + npc->trader_open = npc_trader_open; + npc->market_fromsql = npc_market_fromsql; + npc->market_tosql = npc_market_tosql; + npc->market_delfromsql = npc_market_delfromsql; } diff --git a/src/map/npc.h b/src/map/npc.h index feee8f203..792d85d59 100644 --- a/src/map/npc.h +++ b/src/map/npc.h @@ -14,6 +14,21 @@ struct block_list; struct npc_data; struct view_data; +enum npc_parse_options { + NPO_NONE = 0x0, + NPO_ONINIT = 0x1, + NPO_TRADER = 0x2, +}; + +enum npc_shop_types { + NST_ZENY,/* default */ + NST_CASH,/* official npc cash shop */ + NST_MARKET,/* official npc market type */ + NST_CUSTOM, + /* */ + NST_MAX, +}; + struct npc_timerevent_list { int timer,pos; }; @@ -22,9 +37,15 @@ struct npc_label_list { int pos; }; struct npc_item_list { - unsigned int nameid,value; + unsigned short nameid; + unsigned int value; + unsigned int qty; +}; +struct npc_shop_data { + unsigned char type;/* what am i */ + struct npc_item_list *item;/* list */ + unsigned short items;/* total */ }; - struct npc_data { struct block_list bl; struct unit_data *ud; @@ -60,10 +81,13 @@ struct npc_data { struct npc_timerevent_list *timer_event; int label_list_num; struct npc_label_list *label_list; + /* */ + struct npc_shop_data *shop; + bool trader; } scr; - struct { + struct { /* TODO duck this as soon as the new shop formatting is deemed stable */ struct npc_item_list* shop_item; - int count; + unsigned short count; } shop; struct { short xs,ys; // OnTouch area radius @@ -143,6 +167,9 @@ struct npc_interface { struct npc_data *fake_nd; struct npc_src_list *src_files; struct unit_data base_ud; + /* npc trader global data, for ease of transition between the script, cleared on every usage */ + bool trader_ok; + int trader_funds[2]; /* */ int (*init) (bool minimal); int (*final) (void); @@ -208,7 +235,7 @@ struct npc_interface { const char* (*parse_shop) (char *w1, char *w2, char *w3, char *w4, const char *start, const char *buffer, const char *filepath); void (*convertlabel_db) (struct npc_label_list *label_list, const char *filepath); const char* (*skip_script) (const char *start, const char *buffer, const char *filepath); - const char* (*parse_script) (char *w1, char *w2, char *w3, char *w4, const char *start, const char *buffer, const char *filepath, bool runOnInit); + const char* (*parse_script) (char *w1, char *w2, char *w3, char *w4, const char *start, const char *buffer, const char *filepath, int options); const char* (*parse_duplicate) (char *w1, char *w2, char *w3, char *w4, const char *start, const char *buffer, const char *filepath); int (*duplicate4instance) (struct npc_data *snd, int16 m); void (*setcells) (struct npc_data *nd); @@ -232,6 +259,15 @@ struct npc_interface { void (*do_clear_npc) (void); void (*debug_warps_sub) (struct npc_data *nd); void (*debug_warps) (void); + /* */ + void (*trader_count_funds) (struct npc_data *nd, struct map_session_data *sd); + bool (*trader_pay) (struct npc_data *nd, struct map_session_data *sd, int price, int points); + void (*trader_update) (int master); + int (*market_buylist) (struct map_session_data* sd, unsigned short list_size, struct packet_npc_market_purchase *p); + bool (*trader_open) (struct map_session_data *sd, struct npc_data *nd); + void (*market_fromsql) (void); + void (*market_tosql) (struct npc_data *nd, unsigned short index); + void (*market_delfromsql) (struct npc_data *nd, unsigned short index); /** * For the Secure NPC Timeout option (check config/Secure.h) [RR] **/ diff --git a/src/map/packets.h b/src/map/packets.h index 08c73fdb0..cea916f6d 100644 --- a/src/map/packets.h +++ b/src/map/packets.h @@ -2453,6 +2453,45 @@ packet(0x020d,-1); packet(0x0887,36,clif->pStoragePassword,0); #endif +// 2013-12-23cRagexe - Yommy +#if PACKETVER >= 20131223 + packet(0x0369,7,clif->pActionRequest,2,6); + packet(0x083C,10,clif->pUseSkillToId,2,4,6); + packet(0x0437,5,clif->pWalkToXY,2); + packet(0x035F,6,clif->pTickSend,2); + packet(0x0202,5,clif->pChangeDir,2,4); + packet(0x07E4,6,clif->pTakeItem,2); + packet(0x0362,6,clif->pDropItem,2,4); + packet(0x07EC,8,clif->pMoveToKafra,2,4); + packet(0x0364,8,clif->pMoveFromKafra,2,4); + packet(0x0438,10,clif->pUseSkillToPos,2,4,6,8); + packet(0x0366,90,clif->pUseSkillToPosMoreInfo,2,4,6,8,10); + packet(0x096A,6,clif->pGetCharNameRequest,2); + packet(0x0368,6,clif->pSolveCharName,2); + packet(0x0838,12,clif->pSearchStoreInfoListItemClick,2,6,10); + packet(0x0835,2,clif->pSearchStoreInfoNextPage,0); + packet(0x0819,-1,clif->pSearchStoreInfo,2,4,5,9,13,14,15); + packet(0x0811,-1,clif->pReqTradeBuyingStore,2,4,8,12); + packet(0x0360,6,clif->pReqClickBuyingStore,2); + packet(0x0817,2,clif->pReqCloseBuyingStore,0); + packet(0x0815,-1,clif->pReqOpenBuyingStore,2,4,8,9,89); + packet(0x0365,18,clif->pPartyBookingRegisterReq,2,4); + // packet(0x0363,8); // CZ_JOIN_BATTLE_FIELD + packet(0x0281,-1,clif->pItemListWindowSelected,2,4,8); + packet(0x022d,19,clif->pWantToConnection,2,6,10,14,18); + packet(0x0802,26,clif->pPartyInvite2,2); + // packet(0x0436,4); // CZ_GANGSI_RANK + packet(0x023B,26,clif->pFriendsListAdd,2); + packet(0x0361,5,clif->pHomMenu,2,4); + packet(0x08A4,36,clif->pStoragePassword,0); + /* New */ + packet(0x09d4,2,clif->pNPCShopClosed); + packet(0x09ce,102,clif->pGM_Monster_Item,2); + /* NPC Market */ + packet(0x09d8,2,clif->pNPCMarketClosed); + packet(0x09d6,-1,clif->pNPCMarketPurchase); +#endif + /* PacketKeys: http://hercules.ws/board/topic/1105-hercules-wpe-free-june-14th-patch/ */ #if PACKETVER >= 20110817 packetKeys(0x053D5CED,0x3DED6DED,0x6DED6DED); /* Thanks to Shakto */ @@ -2646,6 +2685,10 @@ packet(0x020d,-1); packetKeys(0x7E241DE0,0x5E805580,0x3D807D80); /* Thanks to Shakto */ #endif +#if PACKETVER >= 20131223 + packetKeys(0x631C511C, 0x111C111C,0x111C111C); +#endif + #if defined(OBFUSCATIONKEY1) && defined(OBFUSCATIONKEY2) && defined(OBFUSCATIONKEY3) packetKeys(OBFUSCATIONKEY1,OBFUSCATIONKEY2,OBFUSCATIONKEY3); #endif diff --git a/src/map/packets_struct.h b/src/map/packets_struct.h index df90b35a4..8f0989f3d 100644 --- a/src/map/packets_struct.h +++ b/src/map/packets_struct.h @@ -201,6 +201,8 @@ enum packet_headers { notifybindonequip = 0x2d3, monsterhpType = 0x977, maptypeproperty2Type = 0x99b, + npcmarketresultackType = 0x9d7, + npcmarketopenType = 0x9d5, }; #pragma pack(push, 1) @@ -895,6 +897,48 @@ struct packet_damage { #endif } __attribute__((packed)); +struct packet_gm_monster_item { + short PacketType; +#if PACKETVER >= 20131218 + char str[100]; +#else + char str[24]; +#endif +} __attribute__((packed)); + +struct packet_npc_market_purchase { + short PacketType; + short PacketLength; + struct { + unsigned short ITID; + int qty; + } list[MAX_INVENTORY];/* assuming MAX_INVENTORY is max since you can't hold more than MAX_INVENTORY items thus cant buy that many at once. */ +} __attribute__((packed)); + +struct packet_npc_market_result_ack { + short PacketType; + short PacketLength; + unsigned char result; + struct { + unsigned short ITID; + unsigned short qty; + unsigned int price; + } list[MAX_INVENTORY];/* assuming MAX_INVENTORY is max since you can't hold more than MAX_INVENTORY items thus cant buy that many at once. */ +} __attribute__((packed)); + +struct packet_npc_market_open { + short PacketType; + short PacketLength; + /* inner struct figured by Ind after some annoying hour of debugging (data Thanks to Yommy) */ + struct { + unsigned short nameid; + unsigned char type; + unsigned int price; + unsigned int qty; + unsigned short view; + } list[1000];/* TODO: whats the actual max of this? */ +} __attribute__((packed)); + #pragma pack(pop) #endif /* _PACKETS_STRUCT_H_ */ diff --git a/src/map/script.c b/src/map/script.c index 074348ef0..a9ad69954 100644 --- a/src/map/script.c +++ b/src/map/script.c @@ -2072,7 +2072,7 @@ void script_set_constant2(const char *name, int value, bool isparameter) { int n = script->add_str(name); if( ( script->str_data[n].type == C_NAME || script->str_data[n].type == C_PARAM ) && ( script->str_data[n].val != 0 || script->str_data[n].backpatch != -1 ) ) { // existing parameter or constant - ShowNotice("Conflicting item/script var '%s', prioritising the script var\n",name); + ShowNotice("Conflicting var name '%s', prioritising the script var\n",name); return; } @@ -18012,6 +18012,272 @@ BUILDIN(instance_set_respawn) { return true; } +/** + * @call openshop({NPC Name}); + * + * @return 1 on success, 0 otherwise. + **/ +BUILDIN(openshop) { + struct npc_data *nd; + struct map_session_data *sd; + const char *name = NULL; + + if( script_hasdata(st, 2) ) { + name = script_getstr(st, 2); + if( !(nd = npc->name2id(name)) || nd->subtype != SCRIPT ) { + ShowWarning("buildin_openshop(\"%s\"): trying to run without a proper NPC!\n",name); + return false; + } + } else if( !(nd = map->id2nd(st->oid)) ) { + ShowWarning("buildin_openshop: trying to run without a proper NPC!\n"); + return false; + } + if( !( sd = script->rid2sd(st) ) ) { + ShowWarning("buildin_openshop: trying to run without a player attached!\n"); + return false; + } else if ( !nd->u.scr.shop || !nd->u.scr.shop->items ) { + ShowWarning("buildin_openshop: trying to open without any items!\n"); + return false; + } + + if( !npc->trader_open(sd,nd) ) + script_pushint(st, 0); + else + script_pushint(st, 1); + + return true; +} +/** + * @call sellitem <Item_ID>,{,price{,qty}}; + * + * adds <Item_ID> (or modifies if present) to shop + * if price not provided (or -1) uses the item's value_sell + **/ +BUILDIN(sellitem) { + struct npc_data *nd; + struct item_data *it; + int i = 0, id = script_getnum(st,2); + int value = 0; + int qty = 0; + + if( !(nd = map->id2nd(st->oid)) ) { + ShowWarning("buildin_sellitem: trying to run without a proper NPC!\n"); + return false; + } else if ( !(it = itemdb->exists(id)) ) { + ShowWarning("buildin_sellitem: unknown item id '%d'!\n",id); + return false; + } + + value = script_hasdata(st,3) ? script_getnum(st, 3) : it->value_buy; + if( value == -1 ) + value = it->value_buy; + + if( !nd->u.scr.shop ) + npc->trader_update(nd->src_id?nd->src_id:nd->bl.id); + else {/* no need to run this if its empty */ + for( i = 0; i < nd->u.scr.shop->items; i++ ) { + if( nd->u.scr.shop->item[i].nameid == id ) + break; + } + } + + if( nd->u.scr.shop->type == NST_MARKET ) { + if( !script_hasdata(st,4) || ( qty = script_getnum(st, 4) ) <= 0 ) { + ShowError("buildin_sellitem: invalid 'qty' for market-type shop!\n"); + return false; + } + } + + if( ( nd->u.scr.shop->type == NST_ZENY || nd->u.scr.shop->type == NST_MARKET ) && value*0.75 < it->value_sell*1.24 ) { + ShowWarning("buildin_sellitem: Item %s [%d] discounted buying price (%d->%d) is less than overcharged selling price (%d->%d) in NPC %s (%s)\n", + it->name, id, value, (int)(value*0.75), it->value_sell, (int)(it->value_sell*1.24), nd->exname, nd->path); + } + + if( i != nd->u.scr.shop->items ) { + nd->u.scr.shop->item[i].value = value; + nd->u.scr.shop->item[i].qty = qty; + if( nd->u.scr.shop->type == NST_MARKET ) /* has been manually updated, make it reflect on sql */ + npc->market_tosql(nd,i); + } else { + for( i = 0; i < nd->u.scr.shop->items; i++ ) { + if( nd->u.scr.shop->item[i].nameid == 0 ) + break; + } + + if( i == nd->u.scr.shop->items ) { + if( nd->u.scr.shop->items == USHRT_MAX ) { + ShowWarning("buildin_sellitem: Can't add %s (%s/%s), shop list is full!\n", it->name, nd->exname, nd->path); + return false; + } + i = nd->u.scr.shop->items; + RECREATE(nd->u.scr.shop->item, struct npc_item_list, ++nd->u.scr.shop->items); + } + + nd->u.scr.shop->item[i].nameid = it->nameid; + nd->u.scr.shop->item[i].value = value; + nd->u.scr.shop->item[i].qty = qty; + } + + return true; +} +/** + * @call stopselling <Item_ID>; + * + * removes <Item_ID> from the current npc shop + * + * @return 1 on success, 0 otherwise + **/ +BUILDIN(stopselling) { + struct npc_data *nd; + int i, id = script_getnum(st,2); + + if( !(nd = map->id2nd(st->oid)) || !nd->u.scr.shop ) { + ShowWarning("buildin_stopselling: trying to run without a proper NPC!\n"); + return false; + } + + for( i = 0; i < nd->u.scr.shop->items; i++ ) { + if( nd->u.scr.shop->item[i].nameid == id ) + break; + } + + if( i != nd->u.scr.shop->items ) { + int cursor; + + if( nd->u.scr.shop->type == NST_MARKET ) + npc->market_delfromsql(nd,i); + + nd->u.scr.shop->item[i].nameid = 0; + nd->u.scr.shop->item[i].value = 0; + nd->u.scr.shop->item[i].qty = 0; + + for( i = 0, cursor = 0; i < nd->u.scr.shop->items; i++ ) { + if( nd->u.scr.shop->item[i].nameid == 0 ) + continue; + + if( cursor != i ) { + nd->u.scr.shop->item[cursor].nameid = nd->u.scr.shop->item[i].nameid; + nd->u.scr.shop->item[cursor].value = nd->u.scr.shop->item[i].value; + nd->u.scr.shop->item[cursor].qty = nd->u.scr.shop->item[i].qty; + } + + cursor++; + } + + script_pushint(st, 1); + } else + script_pushint(st, 0); + + return true; +} +/** + * @call setcurrency <Val1>{,<Val2>}; + * + * updates currently-attached player shop currency + **/ +/* setcurrency(<Val1>,{<Val2>}) */ +BUILDIN(setcurrency) { + int val1 = script_getnum(st,2), + val2 = script_hasdata(st, 3) ? script_getnum(st,3) : 0; + struct npc_data *nd; + + if( !(nd = map->id2nd(st->oid)) ) { + ShowWarning("buildin_setcurrency: trying to run without a proper NPC!\n"); + return false; + } + + npc->trader_funds[0] = val1; + npc->trader_funds[1] = val2; + + return true; +} +/** + * @call tradertype(<type>); + * + * defaults to 0, so no need to call when you're doing zeny + * check enum npc_shop_types for list + * cleans shop list on use + **/ +BUILDIN(tradertype) { + int type = script_getnum(st, 2); + struct npc_data *nd; + + if( !(nd = map->id2nd(st->oid)) ) { + ShowWarning("buildin_tradertype: trying to run without a proper NPC!\n"); + return false; + } else if ( type < 0 || type > NST_MAX ) { + ShowWarning("buildin_tradertype: invalid type param %d!\n",type); + return false; + } + + if( !nd->u.scr.shop ) + npc->trader_update(nd->src_id?nd->src_id:nd->bl.id); + else {/* clear list */ + int i; + for( i = 0; i < nd->u.scr.shop->items; i++ ) { + nd->u.scr.shop->item[i].nameid = 0; + nd->u.scr.shop->item[i].value = 0; + nd->u.scr.shop->item[i].qty = 0; + } + npc->market_delfromsql(nd,USHRT_MAX); + } + + nd->u.scr.shop->type = type; + + return true; +} +/** + * @call purchaseok(); + * + * signs the transaction can proceed + **/ +BUILDIN(purchaseok) { + struct npc_data *nd; + + if( !(nd = map->id2nd(st->oid)) || !nd->u.scr.shop ) { + ShowWarning("buildin_purchaseok: trying to run without a proper NPC!\n"); + return false; + } + + npc->trader_ok = true; + + return true; +} +/** + * @call shopcount(<Item_ID>); + * + * @return number of available items in the script's attached shop + **/ +BUILDIN(shopcount) { + struct npc_data *nd; + int id = script_getnum(st, 2); + unsigned short i; + + if( !(nd = map->id2nd(st->oid)) ) { + ShowWarning("buildin_shopcount(%d): trying to run without a proper NPC!\n",id); + return false; + } else if ( !nd->u.scr.shop || !nd->u.scr.shop->items ) { + ShowWarning("buildin_shopcount(%d): trying to use without any items!\n",id); + return false; + } else if ( nd->u.scr.shop->type != NST_MARKET ) { + ShowWarning("buildin_shopcount(%d): trying to use on a non-NST_MARKET shop!\n",id); + return false; + } + + /* lookup */ + for(i = 0; i < nd->u.scr.shop->items; i++) { + if( nd->u.scr.shop->item[i].nameid == id ) { + script_pushint(st, nd->u.scr.shop->item[i].qty); + break; + } + } + + /* didn't find it */ + if( i == nd->u.scr.shop->items ) + script_pushint(st, 0); + + return true; +} // declarations that were supposed to be exported from npc_chat.c #ifdef PCRE_SUPPORT @@ -18609,6 +18875,15 @@ void script_parse_builtin(void) { BUILDIN_DEF(bg_create_team,"sii"), BUILDIN_DEF(bg_join_team,"i?"), BUILDIN_DEF(bg_match_over,"s?"), + + /* New Shop Support */ + BUILDIN_DEF(openshop,"?"), + BUILDIN_DEF(sellitem,"i??"), + BUILDIN_DEF(stopselling,"i"), + BUILDIN_DEF(setcurrency,"i?"), + BUILDIN_DEF(tradertype,"i"), + BUILDIN_DEF(purchaseok,""), + BUILDIN_DEF(shopcount, "i"), }; int i, len = ARRAYLENGTH(BUILDIN); RECREATE(script->buildin, char *, script->buildin_count + len); // Pre-alloc to speed up diff --git a/src/map/status.c b/src/map/status.c index f6ca1ff00..7d354718d 100644 --- a/src/map/status.c +++ b/src/map/status.c @@ -6060,6 +6060,8 @@ void status_set_viewdata(struct block_list *bl, int class_) sd->vd.cloth_color = 0; if( sd->sc.option&OPTION_HANBOK && battle_config.hanbok_ignorepalette ) sd->vd.cloth_color = 0; + if( sd->sc.option&OPTION_OKTOBERFEST /* TODO: config? */ ) + sd->vd.cloth_color = 0; } } else if (vd) memcpy(&sd->vd, vd, sizeof(struct view_data)); @@ -7463,6 +7465,7 @@ int status_change_start(struct block_list* bl,enum sc_type type,int rate,int val case SC_XMAS: case SC_SUMMER: case SC_HANBOK: + case SC_OKTOBERFEST: if (!vd) return 0; //Store previous values as they could be removed. unit->stop_attack(bl); @@ -8680,6 +8683,7 @@ int status_change_start(struct block_list* bl,enum sc_type type,int rate,int val case SC_XMAS: case SC_SUMMER: case SC_HANBOK: + case SC_OKTOBERFEST: if( !vd ) break; clif->changelook(bl,LOOK_BASE,vd->class_); clif->changelook(bl,LOOK_WEAPON,0); @@ -9067,6 +9071,10 @@ int status_change_start(struct block_list* bl,enum sc_type type,int rate,int val case SC_FUSION: sc->option |= OPTION_FLYING; break; + case SC_OKTOBERFEST: + sc->option |= OPTION_OKTOBERFEST; + opt_flag |= 0x4; + break; default: opt_flag = 0; } @@ -9768,167 +9776,171 @@ int status_change_end_(struct block_list* bl, enum sc_type type, int tid, const opt_flag = 1; switch(type){ - case SC_STONE: - case SC_FREEZE: - case SC_STUN: - case SC_SLEEP: - case SC_DEEP_SLEEP: - case SC_BURNING: - case SC_WHITEIMPRISON: - case SC_COLD: - sc->opt1 = 0; - break; + case SC_STONE: + case SC_FREEZE: + case SC_STUN: + case SC_SLEEP: + case SC_DEEP_SLEEP: + case SC_BURNING: + case SC_WHITEIMPRISON: + case SC_COLD: + sc->opt1 = 0; + break; - case SC_POISON: - case SC_CURSE: - case SC_SILENCE: - case SC_BLIND: - sc->opt2 &= ~(1<<(type-SC_POISON)); - break; - case SC_DPOISON: - sc->opt2 &= ~OPT2_DPOISON; - break; - case SC_CRUCIS: - sc->opt2 &= ~OPT2_SIGNUMCRUCIS; - break; + case SC_POISON: + case SC_CURSE: + case SC_SILENCE: + case SC_BLIND: + sc->opt2 &= ~(1<<(type-SC_POISON)); + break; + case SC_DPOISON: + sc->opt2 &= ~OPT2_DPOISON; + break; + case SC_CRUCIS: + sc->opt2 &= ~OPT2_SIGNUMCRUCIS; + break; - case SC_HIDING: - sc->option &= ~OPTION_HIDE; - opt_flag|= 2|4; //Check for warp trigger + AoE trigger - break; - case SC_CLOAKING: - case SC_CLOAKINGEXCEED: - case SC__INVISIBILITY: - sc->option &= ~OPTION_CLOAK; - case SC_CAMOUFLAGE: - opt_flag|= 2; - break; - case SC_CHASEWALK: - sc->option &= ~(OPTION_CHASEWALK|OPTION_CLOAK); - opt_flag|= 2; - break; - case SC_SIGHT: - sc->option &= ~OPTION_SIGHT; - break; - case SC_WEDDING: - sc->option &= ~OPTION_WEDDING; - opt_flag |= 0x4; - break; - case SC_XMAS: - sc->option &= ~OPTION_XMAS; - opt_flag |= 0x4; - break; - case SC_SUMMER: - sc->option &= ~OPTION_SUMMER; - opt_flag |= 0x4; - break; - case SC_HANBOK: - sc->option &= ~OPTION_HANBOK; - opt_flag |= 0x4; - break; - case SC_ORCISH: - sc->option &= ~OPTION_ORCISH; - break; - case SC_RUWACH: - sc->option &= ~OPTION_RUWACH; - break; - case SC_FUSION: - sc->option &= ~OPTION_FLYING; - break; - //opt3 - case SC_TWOHANDQUICKEN: - case SC_ONEHANDQUICKEN: - case SC_SPEARQUICKEN: - case SC_CONCENTRATION: - case SC_MER_QUICKEN: - sc->opt3 &= ~OPT3_QUICKEN; - opt_flag = 0; - break; - case SC_OVERTHRUST: - case SC_OVERTHRUSTMAX: - case SC_SWOO: - sc->opt3 &= ~OPT3_OVERTHRUST; - if( type == SC_SWOO ) - opt_flag = 8; - else + case SC_HIDING: + sc->option &= ~OPTION_HIDE; + opt_flag|= 2|4; //Check for warp trigger + AoE trigger + break; + case SC_CLOAKING: + case SC_CLOAKINGEXCEED: + case SC__INVISIBILITY: + sc->option &= ~OPTION_CLOAK; + case SC_CAMOUFLAGE: + opt_flag|= 2; + break; + case SC_CHASEWALK: + sc->option &= ~(OPTION_CHASEWALK|OPTION_CLOAK); + opt_flag|= 2; + break; + case SC_SIGHT: + sc->option &= ~OPTION_SIGHT; + break; + case SC_WEDDING: + sc->option &= ~OPTION_WEDDING; + opt_flag |= 0x4; + break; + case SC_XMAS: + sc->option &= ~OPTION_XMAS; + opt_flag |= 0x4; + break; + case SC_SUMMER: + sc->option &= ~OPTION_SUMMER; + opt_flag |= 0x4; + break; + case SC_HANBOK: + sc->option &= ~OPTION_HANBOK; + opt_flag |= 0x4; + break; + case SC_OKTOBERFEST: + sc->option &= ~OPTION_OKTOBERFEST; + opt_flag |= 0x4; + break; + case SC_ORCISH: + sc->option &= ~OPTION_ORCISH; + break; + case SC_RUWACH: + sc->option &= ~OPTION_RUWACH; + break; + case SC_FUSION: + sc->option &= ~OPTION_FLYING; + break; + //opt3 + case SC_TWOHANDQUICKEN: + case SC_ONEHANDQUICKEN: + case SC_SPEARQUICKEN: + case SC_CONCENTRATION: + case SC_MER_QUICKEN: + sc->opt3 &= ~OPT3_QUICKEN; opt_flag = 0; - break; - case SC_ENERGYCOAT: - case SC_SKE: - sc->opt3 &= ~OPT3_ENERGYCOAT; - opt_flag = 0; - break; - case SC_INCATKRATE: //Simulated Explosion spirits effect. - if (bl->type != BL_MOB) - { + break; + case SC_OVERTHRUST: + case SC_OVERTHRUSTMAX: + case SC_SWOO: + sc->opt3 &= ~OPT3_OVERTHRUST; + if( type == SC_SWOO ) + opt_flag = 8; + else + opt_flag = 0; + break; + case SC_ENERGYCOAT: + case SC_SKE: + sc->opt3 &= ~OPT3_ENERGYCOAT; opt_flag = 0; break; - } - case SC_EXPLOSIONSPIRITS: - sc->opt3 &= ~OPT3_EXPLOSIONSPIRITS; - opt_flag = 0; - break; - case SC_STEELBODY: - case SC_SKA: - sc->opt3 &= ~OPT3_STEELBODY; - opt_flag = 0; - break; - case SC_BLADESTOP: - sc->opt3 &= ~OPT3_BLADESTOP; - opt_flag = 0; - break; - case SC_AURABLADE: - sc->opt3 &= ~OPT3_AURABLADE; - opt_flag = 0; - break; - case SC_BERSERK: - opt_flag = 0; - sc->opt3 &= ~OPT3_BERSERK; - break; - // case ???: // doesn't seem to do anything - // sc->opt3 &= ~OPT3_LIGHTBLADE; - // opt_flag = 0; - // break; - case SC_DANCING: - if ((sce->val1&0xFFFF) == CG_MOONLIT) - sc->opt3 &= ~OPT3_MOONLIT; - opt_flag = 0; - break; - case SC_MARIONETTE: - case SC_MARIONETTE_MASTER: - sc->opt3 &= ~OPT3_MARIONETTE; - opt_flag = 0; - break; - case SC_ASSUMPTIO: - sc->opt3 &= ~OPT3_ASSUMPTIO; - opt_flag = 0; - break; - case SC_WARM: //SG skills [Komurka] - sc->opt3 &= ~OPT3_WARM; - opt_flag = 0; - break; - case SC_KAITE: - sc->opt3 &= ~OPT3_KAITE; - opt_flag = 0; - break; - case SC_NJ_BUNSINJYUTSU: - sc->opt3 &= ~OPT3_BUNSIN; - opt_flag = 0; - break; - case SC_SOULLINK: - sc->opt3 &= ~OPT3_SOULLINK; - opt_flag = 0; - break; - case SC_PROPERTYUNDEAD: - sc->opt3 &= ~OPT3_UNDEAD; - opt_flag = 0; - break; - // case ???: // from DA_CONTRACT (looks like biolab mobs aura) - // sc->opt3 &= ~OPT3_CONTRACT; - // opt_flag = 0; - // break; - default: - opt_flag = 0; + case SC_INCATKRATE: //Simulated Explosion spirits effect. + if (bl->type != BL_MOB) + { + opt_flag = 0; + break; + } + case SC_EXPLOSIONSPIRITS: + sc->opt3 &= ~OPT3_EXPLOSIONSPIRITS; + opt_flag = 0; + break; + case SC_STEELBODY: + case SC_SKA: + sc->opt3 &= ~OPT3_STEELBODY; + opt_flag = 0; + break; + case SC_BLADESTOP: + sc->opt3 &= ~OPT3_BLADESTOP; + opt_flag = 0; + break; + case SC_AURABLADE: + sc->opt3 &= ~OPT3_AURABLADE; + opt_flag = 0; + break; + case SC_BERSERK: + opt_flag = 0; + sc->opt3 &= ~OPT3_BERSERK; + break; + // case ???: // doesn't seem to do anything + // sc->opt3 &= ~OPT3_LIGHTBLADE; + // opt_flag = 0; + // break; + case SC_DANCING: + if ((sce->val1&0xFFFF) == CG_MOONLIT) + sc->opt3 &= ~OPT3_MOONLIT; + opt_flag = 0; + break; + case SC_MARIONETTE: + case SC_MARIONETTE_MASTER: + sc->opt3 &= ~OPT3_MARIONETTE; + opt_flag = 0; + break; + case SC_ASSUMPTIO: + sc->opt3 &= ~OPT3_ASSUMPTIO; + opt_flag = 0; + break; + case SC_WARM: //SG skills [Komurka] + sc->opt3 &= ~OPT3_WARM; + opt_flag = 0; + break; + case SC_KAITE: + sc->opt3 &= ~OPT3_KAITE; + opt_flag = 0; + break; + case SC_NJ_BUNSINJYUTSU: + sc->opt3 &= ~OPT3_BUNSIN; + opt_flag = 0; + break; + case SC_SOULLINK: + sc->opt3 &= ~OPT3_SOULLINK; + opt_flag = 0; + break; + case SC_PROPERTYUNDEAD: + sc->opt3 &= ~OPT3_UNDEAD; + opt_flag = 0; + break; + // case ???: // from DA_CONTRACT (looks like biolab mobs aura) + // sc->opt3 &= ~OPT3_CONTRACT; + // opt_flag = 0; + // break; + default: + opt_flag = 0; } if (calc_flag&SCB_DYE) { //Restore DYE color diff --git a/src/map/status.h b/src/map/status.h index cdf3e03d6..1fd354c79 100644 --- a/src/map/status.h +++ b/src/map/status.h @@ -696,6 +696,8 @@ typedef enum sc_type { SC_MOONSTAR, SC_SUPER_STAR, + SC_OKTOBERFEST, + SC_MAX, //Automatically updated max, used in for's to check we are within bounds. } sc_type; // Official status change ids, used to display status icons on the client. @@ -1545,30 +1547,31 @@ enum { }; enum { - OPTION_NOTHING = 0x00000000, - OPTION_SIGHT = 0x00000001, - OPTION_HIDE = 0x00000002, - OPTION_CLOAK = 0x00000004, - OPTION_FALCON = 0x00000010, - OPTION_RIDING = 0x00000020, - OPTION_INVISIBLE = 0x00000040, - OPTION_ORCISH = 0x00000800, - OPTION_WEDDING = 0x00001000, - OPTION_RUWACH = 0x00002000, - OPTION_CHASEWALK = 0x00004000, - OPTION_FLYING = 0x00008000, //Note that clientside Flying and Xmas are 0x8000 for clients prior to 2007. - OPTION_XMAS = 0x00010000, - OPTION_TRANSFORM = 0x00020000, - OPTION_SUMMER = 0x00040000, - OPTION_DRAGON1 = 0x00080000, - OPTION_WUG = 0x00100000, - OPTION_WUGRIDER = 0x00200000, - OPTION_MADOGEAR = 0x00400000, - OPTION_DRAGON2 = 0x00800000, - OPTION_DRAGON3 = 0x01000000, - OPTION_DRAGON4 = 0x02000000, - OPTION_DRAGON5 = 0x04000000, - OPTION_HANBOK = 0x08000000, + OPTION_NOTHING = 0x00000000, + OPTION_SIGHT = 0x00000001, + OPTION_HIDE = 0x00000002, + OPTION_CLOAK = 0x00000004, + OPTION_FALCON = 0x00000010, + OPTION_RIDING = 0x00000020, + OPTION_INVISIBLE = 0x00000040, + OPTION_ORCISH = 0x00000800, + OPTION_WEDDING = 0x00001000, + OPTION_RUWACH = 0x00002000, + OPTION_CHASEWALK = 0x00004000, + OPTION_FLYING = 0x00008000, //Note that clientside Flying and Xmas are 0x8000 for clients prior to 2007. + OPTION_XMAS = 0x00010000, + OPTION_TRANSFORM = 0x00020000, + OPTION_SUMMER = 0x00040000, + OPTION_DRAGON1 = 0x00080000, + OPTION_WUG = 0x00100000, + OPTION_WUGRIDER = 0x00200000, + OPTION_MADOGEAR = 0x00400000, + OPTION_DRAGON2 = 0x00800000, + OPTION_DRAGON3 = 0x01000000, + OPTION_DRAGON4 = 0x02000000, + OPTION_DRAGON5 = 0x04000000, + OPTION_HANBOK = 0x08000000, + OPTION_OKTOBERFEST = 0x10000000, #ifndef NEW_CARTS OPTION_CART1 = 0x00000008, |