summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/mmo.h2
-rw-r--r--src/map/atcommand.c27
-rw-r--r--src/map/clif.c224
-rw-r--r--src/map/clif.h8
-rw-r--r--src/map/map.c3
-rw-r--r--src/map/npc.c566
-rw-r--r--src/map/npc.h46
-rw-r--r--src/map/packets.h43
-rw-r--r--src/map/packets_struct.h44
-rw-r--r--src/map/script.c277
-rw-r--r--src/map/status.c324
-rw-r--r--src/map/status.h51
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,