diff options
Diffstat (limited to 'src/map/npc.c')
-rw-r--r-- | src/map/npc.c | 1637 |
1 files changed, 1246 insertions, 391 deletions
diff --git a/src/map/npc.c b/src/map/npc.c index 1b784b0c8..40ec380ee 100644 --- a/src/map/npc.c +++ b/src/map/npc.c @@ -2,8 +2,8 @@ * This file is part of Hercules. * http://herc.ws - http://github.com/HerculesWS/Hercules * - * Copyright (C) 2012-2015 Hercules Dev Team - * Copyright (C) Athena Dev Teams + * Copyright (C) 2012-2020 Hercules Dev Team + * Copyright (C) Athena Dev Teams * * Hercules is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,6 +25,7 @@ #include "map/battle.h" #include "map/chat.h" +#include "map/clan.h" #include "map/clif.h" #include "map/guild.h" #include "map/instance.h" @@ -35,10 +36,12 @@ #include "map/mob.h" #include "map/pc.h" #include "map/pet.h" +#include "map/quest.h" #include "map/script.h" #include "map/skill.h" #include "map/status.h" #include "map/unit.h" +#include "map/achievement.h" #include "common/HPM.h" #include "common/cbasetypes.h" #include "common/db.h" @@ -59,27 +62,16 @@ #include <string.h> #include <time.h> -struct npc_interface npc_s; +static struct npc_interface npc_s; struct npc_interface *npc; -static int npc_id=START_NPC_NUM; -static int npc_warp=0; -static int npc_shop=0; -static int npc_script=0; -static int npc_mob=0; -static int npc_delay_mob=0; -static int npc_cache_mob=0; - -static const char *npc_last_path; -static const char *npc_last_ref; -struct npc_path_data *npc_last_npd; //For holding the view data of npc classes. [Skotlex] 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 unsigned int npc_market_qty[MAX_INVENTORY]; static struct script_event_s { //Holds pointers to the commonly executed scripts for speedup. [Skotlex] @@ -93,7 +85,7 @@ static struct script_event_s { * @param class_ The NPC class ID. * @return The viewdata, or NULL if the ID is invalid. */ -struct view_data *npc_get_viewdata(int class_) +static struct view_data *npc_get_viewdata(int class_) { if (class_ == INVISIBLE_CLASS) return &npc_viewdb[0]; @@ -115,7 +107,7 @@ struct view_data *npc_get_viewdata(int class_) * @param id The NPC ID to validate. * @return Whether the value is a valid ID. */ -bool npc_db_checkid(int id) +static bool npc_db_checkid(int id) { if (id >= WARP_CLASS && id <= 125) // First subrange return true; @@ -125,22 +117,25 @@ bool npc_db_checkid(int id) return true; if (id >= MAX_NPC_CLASS2_START && id < MAX_NPC_CLASS2_END) // Second range return true; + if (pc->db_checkid(id)) + return true; // Anything else is invalid return false; } /// Returns a new npc id that isn't being used in id_db. /// Fatal error if nothing is available. -int npc_get_new_npc_id(void) { - if( npc_id >= START_NPC_NUM && !map->blid_exists(npc_id) ) - return npc_id++;// available +static int npc_get_new_npc_id(void) +{ + if (npc->npc_id >= START_NPC_NUM && !map->blid_exists(npc->npc_id)) + return npc->npc_id++;// available else {// find next id - int base_id = npc_id; - while( base_id != ++npc_id ) { - if( npc_id < START_NPC_NUM ) - npc_id = START_NPC_NUM; - if( !map->blid_exists(npc_id) ) - return npc_id++;// available + int base_id = npc->npc_id; + while (base_id != ++npc->npc_id) { + if (npc->npc_id < START_NPC_NUM) + npc->npc_id = START_NPC_NUM; + if (!map->blid_exists(npc->npc_id)) + return npc->npc_id++;// available } // full loop, nothing available ShowFatalError("npc_get_new_npc_id: All ids are taken. Exiting..."); @@ -148,7 +143,7 @@ int npc_get_new_npc_id(void) { } } -int npc_isnear_sub(struct block_list *bl, va_list args) +static int npc_isnear_sub(struct block_list *bl, va_list args) { const struct npc_data *nd = NULL; @@ -165,7 +160,8 @@ int npc_isnear_sub(struct block_list *bl, va_list args) return 1; } -bool npc_isnear(struct block_list * bl) { +static bool npc_isnear(struct block_list *bl) +{ if( battle_config.min_npc_vendchat_distance > 0 && map->foreachinrange(npc->isnear_sub,bl, battle_config.min_npc_vendchat_distance, BL_NPC) ) return true; @@ -173,7 +169,7 @@ bool npc_isnear(struct block_list * bl) { return false; } -int npc_ontouch_event(struct map_session_data *sd, struct npc_data *nd) +static int npc_ontouch_event(struct map_session_data *sd, struct npc_data *nd) { char name[EVENT_NAME_LENGTH]; @@ -188,7 +184,7 @@ int npc_ontouch_event(struct map_session_data *sd, struct npc_data *nd) return npc->event(sd,name,1); } -int npc_ontouch2_event(struct map_session_data *sd, struct npc_data *nd) +static int npc_ontouch2_event(struct map_session_data *sd, struct npc_data *nd) { char name[EVENT_NAME_LENGTH]; @@ -201,7 +197,7 @@ int npc_ontouch2_event(struct map_session_data *sd, struct npc_data *nd) return npc->event(sd, name, 2); } -int npc_onuntouch_event(struct map_session_data *sd, struct npc_data *nd) +static int npc_onuntouch_event(struct map_session_data *sd, struct npc_data *nd) { char name[EVENT_NAME_LENGTH]; @@ -217,7 +213,7 @@ int npc_onuntouch_event(struct map_session_data *sd, struct npc_data *nd) /*========================================== * Sub-function of npc_enable, runs OnTouch event when enabled *------------------------------------------*/ -int npc_enable_sub(struct block_list *bl, va_list ap) +static int npc_enable_sub(struct block_list *bl, va_list ap) { struct npc_data *nd; @@ -245,7 +241,7 @@ int npc_enable_sub(struct block_list *bl, va_list ap) /*========================================== * Disable / Enable NPC *------------------------------------------*/ -int npc_enable(const char* name, int flag) +static int npc_enable(const char *name, int flag) { struct npc_data* nd = npc->name2id(name); @@ -283,7 +279,7 @@ int npc_enable(const char* name, int flag) /*========================================== * NPC lookup (get npc_data through npcname) *------------------------------------------*/ -struct npc_data *npc_name2id(const char *name) +static struct npc_data *npc_name2id(const char *name) { return strdb_get(npc->name_db, name); } @@ -293,7 +289,8 @@ struct npc_data *npc_name2id(const char *name) /** * Timer to check for idle time and timeout the dialog if necessary **/ -int npc_rr_secure_timeout_timer(int tid, int64 tick, int id, intptr_t data) { +static int npc_rr_secure_timeout_timer(int tid, int64 tick, int id, intptr_t data) +{ #ifdef SECURE_NPCTIMEOUT struct map_session_data* sd = NULL; unsigned int timeout = NPC_SECURE_TIMEOUT_NEXT; @@ -340,7 +337,7 @@ int npc_rr_secure_timeout_timer(int tid, int64 tick, int id, intptr_t data) { /*========================================== * Dequeue event and add timer for execution (100ms) *------------------------------------------*/ -int npc_event_dequeue(struct map_session_data* sd) +static int npc_event_dequeue(struct map_session_data *sd) { nullpo_ret(sd); @@ -372,7 +369,7 @@ int npc_event_dequeue(struct map_session_data* sd) /** * @see DBCreateData */ -struct DBData npc_event_export_create(union DBKey key, va_list args) +static struct DBData npc_event_export_create(union DBKey key, va_list args) { struct linkdb_node** head_ptr; CREATE(head_ptr, struct linkdb_node*, 1); @@ -384,7 +381,7 @@ struct DBData npc_event_export_create(union DBKey key, va_list args) * exports a npc event label * called from npc_parse_script *------------------------------------------*/ -int npc_event_export(struct npc_data *nd, int i) +static int npc_event_export(struct npc_data *nd, int i) { char* lname; int pos; @@ -410,13 +407,13 @@ int npc_event_export(struct npc_data *nd, int i) return 0; } -int npc_event_sub(struct map_session_data* sd, struct event_data* ev, const char* eventname); //[Lance] +static int npc_event_sub(struct map_session_data *sd, struct event_data *ev, const char *eventname); //[Lance] /** * Exec name (NPC events) on player or global * Do on all NPC when called with foreach */ -void npc_event_doall_sub(void *key, void *data, va_list ap) +static void npc_event_doall_sub(void *key, void *data, va_list ap) { struct event_data* ev = data; int* c; @@ -442,7 +439,7 @@ void npc_event_doall_sub(void *key, void *data, va_list ap) } // runs the specified event (supports both single-npc and global events) -int npc_event_do(const char* name) +static int npc_event_do(const char *name) { nullpo_ret(name); if( name[0] == ':' && name[1] == ':' ) { @@ -459,7 +456,7 @@ int npc_event_do(const char* name) } // runs the specified event, with a RID attached (global only) -int npc_event_doall_id(const char* name, int rid) +static int npc_event_doall_id(const char *name, int rid) { int c = 0; struct linkdb_node **label_linkdb = strdb_get(npc->ev_label_db, name); @@ -472,7 +469,7 @@ int npc_event_doall_id(const char* name, int rid) } // runs the specified event (global only) -int npc_event_doall(const char* name) +static int npc_event_doall(const char *name) { return npc->event_doall_id(name, 0); } @@ -481,7 +478,8 @@ int npc_event_doall(const char* name) * Clock event execution * OnMinute/OnClock/OnHour/OnDay/OnDDHHMM *------------------------------------------*/ -int npc_event_do_clock(int tid, int64 tick, int id, intptr_t data) { +static int npc_event_do_clock(int tid, int64 tick, int id, intptr_t data) +{ static struct tm ev_tm_b; // tracks previous execution time time_t clock; struct tm* t; @@ -533,7 +531,7 @@ int npc_event_do_clock(int tid, int64 tick, int id, intptr_t data) { * OnInit event execution (the start of the event and watch) * @param reload Is the server reloading? **/ -void npc_event_do_oninit( bool reload ) +static void npc_event_do_oninit(bool reload) { ShowStatus("Event '"CL_WHITE"OnInit"CL_RESET"' executed with '"CL_WHITE"%d"CL_RESET"' NPCs."CL_CLL"\n", npc->event_doall("OnInit")); @@ -546,7 +544,7 @@ void npc_event_do_oninit( bool reload ) * Incorporation of the label for the timer event * called from npc_parse_script *------------------------------------------*/ -int npc_timerevent_export(struct npc_data *nd, int i) +static int npc_timerevent_export(struct npc_data *nd, int i) { int t = 0, len = 0; char *lname; @@ -585,7 +583,7 @@ struct timer_event_data { /*========================================== * triger 'OnTimerXXXX' events *------------------------------------------*/ -int npc_timerevent(int tid, int64 tick, int id, intptr_t data) +static int npc_timerevent(int tid, int64 tick, int id, intptr_t data) { int old_rid, old_timer; int64 old_tick; @@ -656,7 +654,8 @@ int npc_timerevent(int tid, int64 tick, int id, intptr_t data) /*========================================== * Start/Resume NPC timer *------------------------------------------*/ -int npc_timerevent_start(struct npc_data* nd, int rid) { +static int npc_timerevent_start(struct npc_data *nd, int rid) +{ int j; int64 tick = timer->gettick(); struct map_session_data *sd = NULL; //Player to whom script is attached. @@ -708,7 +707,7 @@ int npc_timerevent_start(struct npc_data* nd, int rid) { /*========================================== * Stop NPC timer *------------------------------------------*/ -int npc_timerevent_stop(struct npc_data* nd) +static int npc_timerevent_stop(struct npc_data *nd) { struct map_session_data *sd = NULL; int *tid; @@ -742,7 +741,7 @@ int npc_timerevent_stop(struct npc_data* nd) /*========================================== * Aborts a running NPC timer that is attached to a player. *------------------------------------------*/ -void npc_timerevent_quit(struct map_session_data* sd) +static void npc_timerevent_quit(struct map_session_data *sd) { const struct TimerData *td; struct npc_data* nd; @@ -807,7 +806,8 @@ void npc_timerevent_quit(struct map_session_data* sd) * Get the tick value of an NPC timer * If it's stopped, return stopped time *------------------------------------------*/ -int64 npc_gettimerevent_tick(struct npc_data* nd) { +static int64 npc_gettimerevent_tick(struct npc_data *nd) +{ int64 tick; nullpo_ret(nd); @@ -823,7 +823,7 @@ int64 npc_gettimerevent_tick(struct npc_data* nd) { /*========================================== * Set tick for running and stopped timer *------------------------------------------*/ -int npc_settimerevent_tick(struct npc_data* nd, int newtimer) +static int npc_settimerevent_tick(struct npc_data *nd, int newtimer) { bool flag; int old_rid; @@ -847,7 +847,7 @@ int npc_settimerevent_tick(struct npc_data* nd, int newtimer) return 0; } -int npc_event_sub(struct map_session_data* sd, struct event_data* ev, const char* eventname) +static int npc_event_sub(struct map_session_data *sd, struct event_data *ev, const char *eventname) { nullpo_retr(2, sd); nullpo_retr(2, eventname); @@ -878,7 +878,7 @@ int npc_event_sub(struct map_session_data* sd, struct event_data* ev, const char /*========================================== * NPC processing event type *------------------------------------------*/ -int npc_event(struct map_session_data* sd, const char* eventname, int ontouch) +static int npc_event(struct map_session_data *sd, const char *eventname, int ontouch) { struct event_data* ev = (struct event_data*)strdb_get(npc->ev_db, eventname); struct npc_data *nd; @@ -907,7 +907,8 @@ int npc_event(struct map_session_data* sd, const char* eventname, int ontouch) /*========================================== * Sub chk then execute area event type *------------------------------------------*/ -int npc_touch_areanpc_sub(struct block_list *bl, va_list ap) { +static int npc_touch_areanpc_sub(struct block_list *bl, va_list ap) +{ struct map_session_data *sd; int pc_id; char *name; @@ -934,7 +935,8 @@ int npc_touch_areanpc_sub(struct block_list *bl, va_list ap) { * Chk if sd is still touching his assigned npc. * If not, it unsets it and searches for another player in range. *------------------------------------------*/ -int npc_touchnext_areanpc(struct map_session_data* sd, bool leavemap) { +static int npc_touchnext_areanpc(struct map_session_data *sd, bool leavemap) +{ struct npc_data *nd; short xs, ys; @@ -963,7 +965,7 @@ int npc_touchnext_areanpc(struct map_session_data* sd, bool leavemap) { /*========================================== * Exec OnTouch for player if in range of area event *------------------------------------------*/ -int npc_touch_areanpc(struct map_session_data* sd, int16 m, int16 x, int16 y) +static int npc_touch_areanpc(struct map_session_data *sd, int16 m, int16 x, int16 y) { int xs,ys; int f = 1; @@ -1052,7 +1054,7 @@ int npc_touch_areanpc(struct map_session_data* sd, int16 m, int16 x, int16 y) /*========================================== * Exec OnUnTouch for player if out range of area event *------------------------------------------*/ -int npc_untouch_areanpc(struct map_session_data* sd, int16 m, int16 x, int16 y) +static int npc_untouch_areanpc(struct map_session_data *sd, int16 m, int16 x, int16 y) { struct npc_data *nd = NULL; nullpo_retr(1, sd); @@ -1074,7 +1076,7 @@ int npc_untouch_areanpc(struct map_session_data* sd, int16 m, int16 x, int16 y) // OnTouch NPC or Warp for Mobs // Return 1 if Warped -int npc_touch_areanpc2(struct mob_data *md) +static int npc_touch_areanpc2(struct mob_data *md) { int i, m, x, y, id; char eventname[EVENT_NAME_LENGTH]; @@ -1139,7 +1141,8 @@ int npc_touch_areanpc2(struct mob_data *md) //Flag determines the type of object to check for: //&1: NPC Warps //&2: NPCs with on-touch events. -int npc_check_areanpc(int flag, int16 m, int16 x, int16 y, int16 range) { +static int npc_check_areanpc(int flag, int16 m, int16 x, int16 y, int16 range) +{ int i; int x0,y0,x1,y1; int xs,ys; @@ -1198,7 +1201,7 @@ int npc_check_areanpc(int flag, int16 m, int16 x, int16 y, int16 range) { * Chk if player not too far to access the npc. * Returns npc_data (success) or NULL (fail). *------------------------------------------*/ -struct npc_data* npc_checknear(struct map_session_data* sd, struct block_list* bl) +static struct npc_data *npc_checknear(struct map_session_data *sd, struct block_list *bl) { struct npc_data *nd = BL_CAST(BL_NPC, bl); int distance = AREA_SIZE + 1; @@ -1231,7 +1234,7 @@ struct npc_data* npc_checknear(struct map_session_data* sd, struct block_list* b /*========================================== * Make NPC talk in global chat (like npctalk) *------------------------------------------*/ -int npc_globalmessage(const char* name, const char* mes) +static int npc_globalmessage(const char *name, const char *mes) { struct npc_data* nd = npc->name2id(name); char temp[100]; @@ -1249,11 +1252,15 @@ int npc_globalmessage(const char* name, const char* mes) } // MvP tomb [GreenBox] -void run_tomb(struct map_session_data* sd, struct npc_data* nd) { +static void run_tomb(struct map_session_data *sd, struct npc_data *nd) +{ char buffer[200]; char time[10]; nullpo_retv(nd); + + sd->npc_id = nd->bl.id; + strftime(time, sizeof(time), "%H:%M", localtime(&nd->u.tomb.kill_time)); // TODO: Find exact color? @@ -1277,7 +1284,7 @@ void run_tomb(struct map_session_data* sd, struct npc_data* nd) { * NPC 1st call when clicking on npc * Do specific action for NPC type (openshop, run scripts...) *------------------------------------------*/ -int npc_click(struct map_session_data* sd, struct npc_data* nd) +static int npc_click(struct map_session_data *sd, struct npc_data *nd) { nullpo_retr(1, sd); @@ -1328,48 +1335,68 @@ int npc_click(struct map_session_data* sd, struct npc_data* nd) return 0; } -/*========================================== +/** + * Validates a character's script related data and (re-)runs the script if validation was successful. * - *------------------------------------------*/ -int npc_scriptcont(struct map_session_data* sd, int id, bool closing) { - struct block_list *target = map->id2bl(id); + * Is called when: + * - The Next/Close button was clicked. + * - A menu option was selected. + * - A value was entered by input() script command. + * - A progress bar has reached 100%. + * - The character timed out because of idling. + * + * @param sd The character's session data. + * @param id The NPC ID. + * @param closing Whether the script is closing, or not. + * @return 0 on success, otherwise 1. + * +**/ +static int npc_scriptcont(struct map_session_data *sd, int id, bool closing) +{ nullpo_retr(1, sd); - if( id != sd->npc_id ){ - struct npc_data *nd_sd = map->id2nd(sd->npc_id); - struct npc_data *nd = BL_CAST(BL_NPC, target); - ShowDebug("npc_scriptcont: %s (sd->npc_id=%d) is not %s (id=%d).\n", - nd_sd?(char*)nd_sd->name:"'Unknown NPC'", (int)sd->npc_id, - nd?(char*)nd->name:"'Unknown NPC'", (int)id); - return 1; + struct block_list *target = map->id2bl(id); + +#ifdef SECURE_NPCTIMEOUT + if (sd->npc_idle_timer != INVALID_TIMER) { /// Not yet timed out. +#endif + if (id != sd->npc_id) { + struct npc_data *nd_sd = map->id2nd(sd->npc_id); + struct npc_data *nd = BL_CAST(BL_NPC, target); + + ShowDebug("npc_scriptcont: %s (sd->npc_id=%d) is not %s (id=%d).\n", + (nd_sd != NULL) ? nd_sd->name : "'Unknown NPC'", sd->npc_id, + (nd != NULL) ? nd->name : "'Unknown NPC'", id); + + return 1; + } +#ifdef SECURE_NPCTIMEOUT } +#endif - if(id != npc->fake_nd->bl.id) { // Not item script - if ((npc->checknear(sd,target)) == NULL){ - ShowWarning("npc_scriptcont: failed npc->checknear test.\n"); + if (id != npc->fake_nd->bl.id) { /// Not an item script. + if (sd->state.npc_unloaded != 0) { + sd->state.npc_unloaded = 0; + } else if (npc->checknear(sd, target) == NULL) { + ShowWarning("npc_scriptcont: Failed npc->checknear test.\n"); return 1; } } - /** - * For the Secure NPC Timeout option (check config/Secure.h) [RR] - **/ + #ifdef SECURE_NPCTIMEOUT - /** - * Update the last NPC iteration - **/ - sd->npc_idle_tick = timer->gettick(); + sd->npc_idle_tick = timer->gettick(); /// Update the last NPC iteration. #endif - /** - * WPE can get to this point with a progressbar; we deny it. - **/ - if( sd->progressbar.npc_id && DIFF_TICK(sd->progressbar.timeout,timer->gettick()) > 0 ) + /// WPE can get to this point with a progressbar; we deny it. + if (sd->progressbar.npc_id != 0 && DIFF_TICK(sd->progressbar.timeout, timer->gettick()) > 0) return 1; - if( !sd->st ) + if (sd->st == NULL) { + sd->npc_id = 0; return 1; + } - if( closing && sd->st->state == CLOSE ) + if (closing && sd->st->state == CLOSE) sd->st->state = END; script->run_main(sd->st); @@ -1380,7 +1407,8 @@ int npc_scriptcont(struct map_session_data* sd, int id, bool closing) { /*========================================== * Chk if valid call then open buy or selling list *------------------------------------------*/ -int npc_buysellsel(struct map_session_data* sd, int id, int type) { +static int npc_buysellsel(struct map_session_data *sd, int id, int type) +{ struct npc_data *nd; nullpo_retr(1, sd); @@ -1419,9 +1447,9 @@ int npc_buysellsel(struct map_session_data* sd, int id, int type) { } /*========================================== -* Cash Shop Buy List -*------------------------------------------*/ -int npc_cashshop_buylist(struct map_session_data *sd, int points, struct itemlist *item_list) + * Cash Shop Buy List + *------------------------------------------*/ +static int npc_cashshop_buylist(struct map_session_data *sd, int points, struct itemlist *item_list) { int i, j, new_, w, vt; struct npc_data *nd = NULL; @@ -1444,11 +1472,16 @@ int npc_cashshop_buylist(struct map_session_data *sd, int points, struct itemlis return ERROR_TYPE_NPC; 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 ) { + if (nd->subtype == SCRIPT && nd->u.scr.shop && + nd->u.scr.shop->type != NST_ZENY && + nd->u.scr.shop->type != NST_MARKET && + nd->u.scr.shop->type != NST_BARTER && + nd->u.scr.shop->type != NST_EXPANDED_BARTER) { shop = nd->u.scr.shop->item; shop_size = nd->u.scr.shop->items; - } else + } else { return ERROR_TYPE_NPC; + } } else { shop = nd->u.shop.shop_item; shop_size = nd->u.shop.count; @@ -1522,7 +1555,7 @@ int npc_cashshop_buylist(struct map_session_data *sd, int points, struct itemlis } //npc_buylist for script-controlled shops. -int npc_buylist_sub(struct map_session_data *sd, struct itemlist *item_list, struct npc_data *nd) +static int npc_buylist_sub(struct map_session_data *sd, struct itemlist *item_list, struct npc_data *nd) { char npc_ev[EVENT_NAME_LENGTH]; int i; @@ -1554,7 +1587,7 @@ int npc_buylist_sub(struct map_session_data *sd, struct itemlist *item_list, str /** * Loads persistent NPC Market Data from SQL **/ -void npc_market_fromsql(void) +static void npc_market_fromsql(void) { struct SqlStmt *stmt = SQL->StmtMalloc(map->mysql_handle); char name[NAME_LENGTH+1]; @@ -1569,9 +1602,9 @@ void npc_market_fromsql(void) 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); + SQL->StmtBindColumn(stmt, 0, SQLDT_STRING, &name, sizeof name, NULL, NULL); + SQL->StmtBindColumn(stmt, 1, SQLDT_INT, &itemid, sizeof itemid, NULL, NULL); + SQL->StmtBindColumn(stmt, 2, SQLDT_INT, &amount, sizeof amount, NULL, NULL); while ( SQL_SUCCESS == SQL->StmtNextRow(stmt) ) { struct npc_data *nd = NULL; @@ -1579,11 +1612,11 @@ void npc_market_fromsql(void) if( !(nd = npc->name2id(name)) ) { ShowError("npc_market_fromsql: NPC '%s' not found! skipping...\n",name); - npc->market_delfromsql_sub(name, USHRT_MAX); + npc->market_delfromsql_sub(name, INT_MAX); continue; - } else if ( nd->subtype != SCRIPT || !nd->u.scr.shop || !nd->u.scr.shop->items || nd->u.scr.shop->type != NST_MARKET ) { + } 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); - npc->market_delfromsql_sub(name, USHRT_MAX); + npc->market_delfromsql_sub(name, INT_MAX); continue; } @@ -1605,18 +1638,20 @@ void npc_market_fromsql(void) /** * Saves persistent NPC Market Data into SQL **/ -void npc_market_tosql(struct npc_data *nd, unsigned short index) { +static void npc_market_tosql(struct npc_data *nd, int index) +{ nullpo_retv(nd); - Assert_retv(index < nd->u.scr.shop->items); - if (SQL_ERROR == SQL->Query(map->mysql_handle, "REPLACE INTO `%s` VALUES ('%s','%d','%u')", + Assert_retv(index >= 0 && index < nd->u.scr.shop->items); + if (SQL_ERROR == SQL->Query(map->mysql_handle, "REPLACE INTO `%s` VALUES ('%s','%d','%d')", map->npc_market_data_db, 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_sub(const char *npcname, unsigned short index) { - if( index == USHRT_MAX ) { +static void npc_market_delfromsql_sub(const char *npcname, int index) +{ + if (index == INT_MAX ) { if( SQL_ERROR == SQL->Query(map->mysql_handle, "DELETE FROM `%s` WHERE `name`='%s'", map->npc_market_data_db, npcname) ) Sql_ShowDebug(map->mysql_handle); } else { @@ -1628,15 +1663,295 @@ void npc_market_delfromsql_sub(const char *npcname, unsigned short index) { /** * Removes persistent NPC Market Data from SQL **/ -void npc_market_delfromsql(struct npc_data *nd, unsigned short index) { +static void npc_market_delfromsql(struct npc_data *nd, int index) +{ + nullpo_retv(nd); + Assert_retv(index == INT_MAX || (index >= 0 && index < nd->u.scr.shop->items)); + npc->market_delfromsql_sub(nd->exname, index == INT_MAX ? index : nd->u.scr.shop->item[index].nameid); +} + +/** + * Loads persistent NPC Barter Data from SQL + **/ +static void npc_barter_fromsql(void) +{ + struct SqlStmt *stmt = SQL->StmtMalloc(map->mysql_handle); + char name[NAME_LENGTH + 1]; + int itemid; + int amount; + int removeId; + int removeAmount; + + if (SQL_ERROR == SQL->StmtPrepare(stmt, "SELECT `name`, `itemId`, `amount`, `priceId`, `priceAmount` FROM `%s`", map->npc_barter_data_db) + || SQL_ERROR == SQL->StmtExecute(stmt) + ) { + SqlStmt_ShowDebug(stmt); + SQL->StmtFree(stmt); + return; + } + + SQL->StmtBindColumn(stmt, 0, SQLDT_STRING, &name, sizeof name, NULL, NULL); + SQL->StmtBindColumn(stmt, 1, SQLDT_INT, &itemid, sizeof itemid, NULL, NULL); + SQL->StmtBindColumn(stmt, 2, SQLDT_UINT32, &amount, sizeof amount, NULL, NULL); + SQL->StmtBindColumn(stmt, 3, SQLDT_INT, &removeId, sizeof removeId, NULL, NULL); + SQL->StmtBindColumn(stmt, 4, SQLDT_INT, &removeAmount, sizeof removeAmount, NULL, NULL); + + while (SQL_SUCCESS == SQL->StmtNextRow(stmt)) { + struct npc_data *nd = NULL; + unsigned short i; + + if (!(nd = npc->name2id(name))) { + ShowError("npc_barter_fromsql: NPC '%s' not found! skipping...\n",name); + npc->barter_delfromsql_sub(name, INT_MAX, 0, 0); + continue; + } else if (nd->subtype != SCRIPT || !nd->u.scr.shop || !nd->u.scr.shop->items || nd->u.scr.shop->type != NST_BARTER) { + ShowError("npc_barter_fromsql: NPC '%s' is not proper for barter, skipping...\n",name); + npc->barter_delfromsql_sub(name, INT_MAX, 0, 0); + continue; + } + + for (i = 0; i < nd->u.scr.shop->items; i++) { + struct npc_item_list *const item = &nd->u.scr.shop->item[i]; + if (item->nameid == itemid && item->value == removeId && item->value2 == removeAmount) { + item->qty = amount; + break; + } + } + + if (i == nd->u.scr.shop->items) { + ShowError("npc_barter_fromsql: NPC '%s' does not sell item %d (qty %d), deleting...\n", name, itemid, amount); + npc->barter_delfromsql_sub(name, itemid, removeId, removeAmount); + continue; + } + } + SQL->StmtFree(stmt); +} + +/** + * Saves persistent NPC Barter Data into SQL + **/ +static void npc_barter_tosql(struct npc_data *nd, int index) +{ nullpo_retv(nd); - Assert_retv(index == USHRT_MAX || index < nd->u.scr.shop->items); - npc->market_delfromsql_sub(nd->exname, index == USHRT_MAX ? index : nd->u.scr.shop->item[index].nameid); + Assert_retv(index >= 0 && index < nd->u.scr.shop->items); + const struct npc_item_list *const item = &nd->u.scr.shop->item[index]; + if (item->qty == -1) + return; + if (SQL_ERROR == SQL->Query(map->mysql_handle, "REPLACE INTO `%s` VALUES ('%s', '%d', '%d', '%u', '%d')", + map->npc_barter_data_db, nd->exname, item->nameid, item->qty, item->value, item->value2)) { + Sql_ShowDebug(map->mysql_handle); + } } + +/** + * Removes persistent NPC Barter Data from SQL + */ +static void npc_barter_delfromsql_sub(const char *npcname, int itemId, int itemId2, int amount2) +{ + if (itemId == INT_MAX) { + if (SQL_ERROR == SQL->Query(map->mysql_handle, "DELETE FROM `%s` WHERE `name`='%s'", map->npc_barter_data_db, npcname)) + Sql_ShowDebug(map->mysql_handle); + } else { + if (SQL_ERROR == SQL->Query(map->mysql_handle, "DELETE FROM `%s` WHERE `name`='%s' AND `itemId`='%d' AND `priceId`='%d' AND `priceAmount`='%d' LIMIT 1", + map->npc_barter_data_db, npcname, itemId, itemId2, amount2)) { + Sql_ShowDebug(map->mysql_handle); + } + } +} + +/** + * Removes persistent NPC Barter Data from SQL + **/ +static void npc_barter_delfromsql(struct npc_data *nd, int index) +{ + nullpo_retv(nd); + if (index == INT_MAX) { + npc->barter_delfromsql_sub(nd->exname, INT_MAX, 0, 0); + } else { + Assert_retv(index >= 0 && index < nd->u.scr.shop->items); + const struct npc_item_list *const item = &nd->u.scr.shop->item[index]; + npc->barter_delfromsql_sub(nd->exname, item->nameid, item->value, item->value2); + } +} + + +/** + * Loads persistent NPC Expanded Barter Data from SQL + **/ +static void npc_expanded_barter_fromsql(void) +{ + struct SqlStmt *stmt = SQL->StmtMalloc(map->mysql_handle); + char name[NAME_LENGTH + 1]; + int itemid; + int amount; + int zeny; + StringBuf buf; + + StrBuf->Init(&buf); + StrBuf->AppendStr(&buf, "SELECT `name`, `itemId`, `amount`, `zeny`"); + for (int k = 1; k < 11; k ++) { + StrBuf->Printf(&buf, ", `currencyId%d`, `currencyAmount%d`, `currencyRefine%d`", k, k, k); + } + StrBuf->Printf(&buf, " FROM `%s`", map->npc_expanded_barter_data_db); + + if (SQL_ERROR == SQL->StmtPrepareStr(stmt, StrBuf->Value(&buf)) + || SQL_ERROR == SQL->StmtExecute(stmt) + ) { + SqlStmt_ShowDebug(stmt); + SQL->StmtFree(stmt); + StrBuf->Destroy(&buf); + return; + } + + struct npc_barter_currency tempCurrency[10]; + SQL->StmtBindColumn(stmt, 0, SQLDT_STRING, &name, sizeof name, NULL, NULL); + SQL->StmtBindColumn(stmt, 1, SQLDT_INT, &itemid, sizeof itemid, NULL, NULL); + SQL->StmtBindColumn(stmt, 2, SQLDT_UINT32, &amount, sizeof amount, NULL, NULL); + SQL->StmtBindColumn(stmt, 3, SQLDT_UINT32, &zeny, sizeof zeny, NULL, NULL); + for (int k = 0; k < 10; k ++) { + SQL->StmtBindColumn(stmt, k * 3 + 4, SQLDT_INT, &tempCurrency[k].nameid, sizeof tempCurrency[k].nameid, NULL, NULL); + SQL->StmtBindColumn(stmt, k * 3 + 5, SQLDT_INT, &tempCurrency[k].amount, sizeof tempCurrency[k].amount, NULL, NULL); + SQL->StmtBindColumn(stmt, k * 3 + 6, SQLDT_INT, &tempCurrency[k].refine, sizeof tempCurrency[k].refine, NULL, NULL); + } + + while (SQL_SUCCESS == SQL->StmtNextRow(stmt)) { + struct npc_data *nd = NULL; + unsigned short i; + + if ((nd = npc->name2id(name)) == NULL) { + ShowError("npc_expanded_barter_fromsql: NPC '%s' not found! skipping...\n",name); + npc->expanded_barter_delfromsql_sub(name, INT_MAX, 0, 0, NULL); + continue; + } else if (nd->subtype != SCRIPT || nd->u.scr.shop == NULL || nd->u.scr.shop->items == 0 || nd->u.scr.shop->type != NST_EXPANDED_BARTER) { + ShowError("npc_expanded_barter_fromsql: NPC '%s' is not proper for barter, skipping...\n",name); + npc->expanded_barter_delfromsql_sub(name, INT_MAX, 0, 0, NULL); + continue; + } + + for (i = 0; i < nd->u.scr.shop->items; i++) { + struct npc_item_list *const item = &nd->u.scr.shop->item[i]; + if (item->nameid == itemid && item->value == zeny) { + int count = nd->u.scr.shop->item[i].value2; + if (count > 10) + count = 10; + int curIndex; + for (curIndex = 0; curIndex < count; curIndex ++) { + struct npc_barter_currency *currency = &nd->u.scr.shop->item[i].currency[curIndex]; + struct npc_barter_currency *currency2 = &tempCurrency[curIndex]; + if (currency->nameid != currency2->nameid || + currency->amount != currency2->amount || + currency->refine != currency2->refine) { + break; + } + } + if (curIndex == count) { + item->qty = amount; + break; + } + } + } + + if (i == nd->u.scr.shop->items) { + ShowError("npc_expanded_barter_fromsql: NPC '%s' does not sell item %d (qty %d), deleting...\n", name, itemid, amount); + npc->expanded_barter_delfromsql_sub(name, itemid, zeny, 10, &tempCurrency[0]); + continue; + } + } + SQL->StmtFree(stmt); + StrBuf->Destroy(&buf); +} + +/** + * Saves persistent NPC Expanded Barter Data into SQL + **/ +static void npc_expanded_barter_tosql(struct npc_data *nd, int index) +{ + nullpo_retv(nd); + Assert_retv(index >= 0 && index < nd->u.scr.shop->items); + const struct npc_item_list *const item = &nd->u.scr.shop->item[index]; + if (item->qty == -1) + return; + + npc->expanded_barter_delfromsql(nd, index); + + StringBuf buf; + StrBuf->Init(&buf); + StrBuf->Printf(&buf, "INSERT INTO `%s` VALUES ('%s', '%d', '%d', '%u'", map->npc_expanded_barter_data_db, nd->exname, item->nameid, item->qty, item->value); + int currencyCount = item->value2; + if (currencyCount > 10) + currencyCount = 10; + int k; + for (k = 0; k < currencyCount; k++) { + struct npc_barter_currency *currency = &item->currency[k]; + StrBuf->Printf(&buf, ", '%d', '%d', '%d'", currency->nameid, currency->amount, currency->refine); + } + for (; k < 10; k ++) { + StrBuf->Printf(&buf, ", '0', '0', '0'"); + } + StrBuf->AppendStr(&buf, ")"); + + if (SQL_ERROR == SQL->QueryStr(map->mysql_handle, StrBuf->Value(&buf))) { + Sql_ShowDebug(map->mysql_handle); + } + StrBuf->Destroy(&buf); +} + +/** + * Removes persistent NPC Expanded Barter Data from SQL + */ +static void npc_expanded_barter_delfromsql_sub(const char *npcname, int itemId, int zeny, int currencyCount, struct npc_barter_currency* currency) +{ + if (itemId == INT_MAX) { + if (SQL_ERROR == SQL->Query(map->mysql_handle, "DELETE FROM `%s` WHERE `name`='%s'", map->npc_expanded_barter_data_db, npcname)) + Sql_ShowDebug(map->mysql_handle); + } else { + StringBuf buf; + + StrBuf->Init(&buf); + StrBuf->Printf(&buf, "DELETE FROM `%s` WHERE `name`='%s' AND `itemId`='%d' AND `zeny`='%d'", + map->npc_expanded_barter_data_db, npcname, itemId, zeny); + int k = 0; + if (currencyCount > 10) + currencyCount = 10; + for (k = 0; k < currencyCount; k++) { + struct npc_barter_currency *currency1 = ¤cy[k]; + StrBuf->Printf(&buf, " AND currencyId%d='%d' and currencyAmount%d='%d' and currencyRefine%d='%d'", + k + 1, currency1->nameid, k + 1, currency1->amount, k + 1, currency1->refine); + } + for (; k < 10; k ++) { + StrBuf->Printf(&buf, " AND currencyId%d='0' and currencyAmount%d='0' and currencyRefine%d='0'", + k + 1, k + 1, k + 1); + } + StrBuf->AppendStr(&buf, " LIMIT 1"); + + if (SQL_ERROR == SQL->QueryStr(map->mysql_handle, StrBuf->Value(&buf))) { + Sql_ShowDebug(map->mysql_handle); + } + StrBuf->Destroy(&buf); + } +} + + +/** + * Removes persistent NPC Expanded Barter Data from SQL + **/ +static void npc_expanded_barter_delfromsql(struct npc_data *nd, int index) +{ + nullpo_retv(nd); + if (index == INT_MAX) { + npc->expanded_barter_delfromsql_sub(nd->exname, INT_MAX, 0, 0, NULL); + } else { + Assert_retv(index >= 0 && index < nd->u.scr.shop->items); + const struct npc_item_list *const item = &nd->u.scr.shop->item[index]; + npc->expanded_barter_delfromsql_sub(nd->exname, item->nameid, item->value, item->value2, &item->currency[0]); + } +} + /** * Judges whether to allow and spawn a trader's window. **/ -bool npc_trader_open(struct map_session_data *sd, struct npc_data *nd) { +static bool npc_trader_open(struct map_session_data *sd, struct npc_data *nd) +{ nullpo_retr(false, sd); nullpo_retr(false, nd); if( !nd->u.scr.shop || !nd->u.scr.shop->items ) @@ -1664,6 +1979,12 @@ bool npc_trader_open(struct map_session_data *sd, struct npc_data *nd) { clif->npc_market_open(sd,nd); } break; + case NST_BARTER: + clif->npc_barter_open(sd, nd); + break; + case NST_EXPANDED_BARTER: + clif->npc_expanded_barter_open(sd, nd); + break; default: clif->cashshop_show(sd,nd); break; @@ -1676,7 +1997,8 @@ bool npc_trader_open(struct map_session_data *sd, struct npc_data *nd) { * * @param master id of the original npc **/ -void npc_trader_update(int master) { +static void npc_trader_update(int master) +{ struct DBIterator *iter; struct block_list* bl; struct npc_data *master_nd = map->id2nd(master); @@ -1700,7 +2022,8 @@ void npc_trader_update(int master) { * @param nd shop * @param sd player **/ -void npc_trader_count_funds(struct npc_data *nd, struct map_session_data *sd) { +static void npc_trader_count_funds(struct npc_data *nd, struct map_session_data *sd) +{ char evname[EVENT_NAME_LENGTH]; struct event_data *ev = NULL; @@ -1740,7 +2063,8 @@ void npc_trader_count_funds(struct npc_data *nd, struct map_session_data *sd) { * * @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) { +static 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; @@ -1750,8 +2074,8 @@ bool npc_trader_pay(struct npc_data *nd, struct map_session_data *sd, int price, 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); + pc->setreg(sd,script->add_variable("@price"),price); + pc->setreg(sd,script->add_variable("@points"),points); script->run_npc(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); @@ -1761,7 +2085,8 @@ bool npc_trader_pay(struct npc_data *nd, struct map_session_data *sd, int price, /*========================================== * Cash Shop Buy *------------------------------------------*/ -int npc_cashshop_buy(struct map_session_data *sd, int nameid, int amount, int points) { +static int npc_cashshop_buy(struct map_session_data *sd, int nameid, int amount, int points) +{ struct npc_data *nd = NULL; struct item_data *item; struct npc_item_list *shop = NULL; @@ -1786,11 +2111,16 @@ int npc_cashshop_buy(struct map_session_data *sd, int nameid, int amount, int po return ERROR_TYPE_ITEM_ID; // Invalid Item 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 ) { + if (nd->subtype == SCRIPT && nd->u.scr.shop && + nd->u.scr.shop->type != NST_ZENY && + nd->u.scr.shop->type != NST_MARKET && + nd->u.scr.shop->type != NST_BARTER && + nd->u.scr.shop->type != NST_EXPANDED_BARTER) { shop = nd->u.scr.shop->item; shop_size = nd->u.scr.shop->items; - } else + } else { return ERROR_TYPE_NPC; + } } else { shop = nd->u.shop.shop_item; shop_size = nd->u.shop.count; @@ -1866,7 +2196,7 @@ int npc_cashshop_buy(struct map_session_data *sd, int nameid, int amount, int po * @param item_list List of items. * @return result code for clif->parse_NpcBuyListSend. */ -int npc_buylist(struct map_session_data *sd, struct itemlist *item_list) +static int npc_buylist(struct map_session_data *sd, struct itemlist *item_list) { struct npc_data* nd; struct npc_item_list *shop = NULL; @@ -1935,7 +2265,11 @@ int npc_buylist(struct map_session_data *sd, struct itemlist *item_list) break; case ADDITEM_OVERAMOUNT: +#if PACKETVER >= 20110705 + return 9; +#else return 2; +#endif } value = pc->modifybuyvalue(sd,value); @@ -1989,7 +2323,7 @@ int npc_buylist(struct map_session_data *sd, struct itemlist *item_list) /** * Processes incoming npc market purchase list **/ -int npc_market_buylist(struct map_session_data *sd, struct itemlist *item_list) +static enum market_buy_result npc_market_buylist(struct map_session_data *sd, struct itemlist *item_list) { struct npc_data* nd; struct npc_item_list *shop = NULL; @@ -2003,7 +2337,7 @@ int npc_market_buylist(struct map_session_data *sd, struct itemlist *item_list) nd = npc->checknear(sd,map->id2bl(sd->npc_shopid)); if (nd == NULL || nd->subtype != SCRIPT || VECTOR_LENGTH(*item_list) == 0 || !nd->u.scr.shop || nd->u.scr.shop->type != NST_MARKET) - return 1; + return MARKET_BUY_RESULT_ERROR; shop = nd->u.scr.shop->item; shop_size = nd->u.scr.shop->items; @@ -2023,18 +2357,18 @@ int npc_market_buylist(struct map_session_data *sd, struct itemlist *item_list) entry->id == 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 + return MARKET_BUY_RESULT_ERROR; // no such item in shop entry->id = shop[j].nameid; //item_avail replacement if (entry->amount > (int)shop[j].qty) - return 1; + return MARKET_BUY_RESULT_AMOUNT_TOO_BIG; value = shop[j].value; npc_market_qty[i] = j; if (!itemdb->exists(entry->id)) /* TODO find official response for this */ - return 1; // item no longer in itemdb + return MARKET_BUY_RESULT_ERROR; // item no longer in itemdb if (!itemdb->isstackable(entry->id) && entry->amount > 1) { //Exploit? You can't buy more than 1 of equipment types o.O @@ -2058,13 +2392,13 @@ int npc_market_buylist(struct map_session_data *sd, struct itemlist *item_list) } if (z > sd->status.zeny) /* TODO find official response for this */ - return 1; // Not enough Zeny + return MARKET_BUY_RESULT_NO_ZENY; // Not enough Zeny if( w + sd->weight > sd->max_weight ) /* TODO find official response for this */ - return 1; // Too heavy + return MARKET_BUY_RESULT_OVER_WEIGHT; // Too heavy if( pc->inventoryblank(sd) < new_ ) /* TODO find official response for this */ - return 1; // Not enough space to store items + return MARKET_BUY_RESULT_OUT_OF_SPACE; // Not enough space to store items pc->payzeny(sd,(int)z,LOG_TYPE_NPC, NULL); @@ -2074,7 +2408,7 @@ int npc_market_buylist(struct map_session_data *sd, struct itemlist *item_list) j = npc_market_qty[i]; if (entry->amount > (int)shop[j].qty) /* wohoo someone tampered with the packet. */ - return 1; + return MARKET_BUY_RESULT_AMOUNT_TOO_BIG; shop[j].qty -= entry->amount; @@ -2092,14 +2426,292 @@ int npc_market_buylist(struct map_session_data *sd, struct itemlist *item_list) } } - return 0; + return MARKET_BUY_RESULT_SUCCESS; +} + +/** + * Processes incoming npc barter purchase list + **/ +static int npc_barter_buylist(struct map_session_data *sd, struct barteritemlist *item_list) +{ + struct npc_data* nd; + struct npc_item_list *shop = NULL; + int w, new_; + unsigned short shop_size = 0; + + nullpo_retr(1, sd); + nullpo_retr(1, item_list); + + nd = npc->checknear(sd, map->id2bl(sd->npc_shopid)); + + if (nd == NULL || nd->subtype != SCRIPT || VECTOR_LENGTH(*item_list) == 0 || !nd->u.scr.shop || nd->u.scr.shop->type != NST_BARTER) + return 11; + + shop = nd->u.scr.shop->item; + shop_size = nd->u.scr.shop->items; + + w = 0; + new_ = 0; + + int items[MAX_INVENTORY] = { 0 }; + + // process entries in buy list, one by one + for (int i = 0; i < VECTOR_LENGTH(*item_list); ++i) { + struct barter_itemlist_entry *entry = &VECTOR_INDEX(*item_list, i); + + const int n = entry->removeIndex; + if (n < 0 || n >= sd->status.inventorySize) + return 11; // wrong inventory index + + if (entry->addAmount <= 0) + return 14; // not enough item amount in inventory + + int removeId = sd->status.inventory[n].nameid; + const int j = entry->shopIndex; + if (j < 0 || j >= shop_size) + return 13; // no such item in shop + if (entry->addId != shop[j].nameid && entry->addId != itemdb_viewid(shop[j].nameid)) + return 13; // no such item in shop + if (removeId != shop[j].value && removeId != itemdb_viewid(shop[j].value)) + return 13; // no such item in shop + entry->addId = shop[j].nameid; // item_avail replacement + removeId = shop[j].value; // item_avail replacement + + if (!itemdb->exists(entry->addId)) + return 13; // item no longer in itemdb + + const int removeAmount = shop[j].value2; + + if ((int)shop[j].qty != -1 && entry->addAmount > (int)shop[j].qty) + return 14; // not enough item amount in shop + + if (removeAmount * entry->addAmount > sd->status.inventory[n].amount) + return 14; // not enough item amount in inventory + + items[n] += removeAmount * entry->addAmount; + + if (items[n] > sd->status.inventory[n].amount) + return 14; // not enough item amount in inventory + + entry->addId = shop[j].nameid; //item_avail replacement + + npc_market_qty[i] = j; + + if (!itemdb->isstackable(entry->addId) && entry->addAmount > 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 non-stackable item %d!\n", + sd->status.name, sd->status.account_id, sd->status.char_id, entry->addAmount, entry->addId); + entry->addAmount = 1; + } + + switch (pc->checkadditem(sd, entry->addId, entry->addAmount)) { + case ADDITEM_EXIST: + break; + case ADDITEM_NEW: + new_++; + break; + case ADDITEM_OVERAMOUNT: /* TODO find official response for this */ + return 1; + } + + w += itemdb_weight(entry->addId) * entry->addAmount; + w -= itemdb_weight(removeId) * removeAmount; + } + + if (w + sd->weight > sd->max_weight) + return 2; // Too heavy + + if (pc->inventoryblank(sd) < new_) + return 3; // Not enough space to store items + + for (int i = 0; i < sd->status.inventorySize; ++i) { + const int removeAmountTotal = items[i]; + if (removeAmountTotal == 0) + continue; + if (pc->delitem(sd, i, removeAmountTotal, 0, DELITEM_SOLD, LOG_TYPE_NPC) != 0) { + return 11; // unknown exploit + } + } + + for (int i = 0; i < VECTOR_LENGTH(*item_list); ++i) { + struct barter_itemlist_entry *entry = &VECTOR_INDEX(*item_list, i); + const int shopIdx = npc_market_qty[i]; + + if ((int)shop[shopIdx].qty != -1) { + if (entry->addAmount > (int)shop[shopIdx].qty) /* wohoo someone tampered with the packet. */ + return 14; + shop[shopIdx].qty -= entry->addAmount; + } + + npc->barter_tosql(nd, shopIdx); + + if (itemdb_type(entry->addId) == IT_PETEGG) { + pet->create_egg(sd, entry->addId); + } else { + struct item item_tmp; + memset(&item_tmp, 0, sizeof(item_tmp)); + item_tmp.nameid = entry->addId; + item_tmp.identify = 1; + pc->additem(sd, &item_tmp, entry->addAmount, LOG_TYPE_NPC); + } + } + + return 12; +} + + +/** + * Processes incoming npc expanded barter purchase list + **/ +static int npc_expanded_barter_buylist(struct map_session_data *sd, struct barteritemlist *item_list) +{ + nullpo_retr(1, sd); + nullpo_retr(1, item_list); + + struct npc_data* nd = npc->checknear(sd, map->id2bl(sd->npc_shopid)); + + if (nd == NULL || nd->subtype != SCRIPT || VECTOR_LENGTH(*item_list) == 0 || + !nd->u.scr.shop || nd->u.scr.shop->type != NST_EXPANDED_BARTER) { + return 11; + } + + struct npc_item_list *shop = nd->u.scr.shop->item; + unsigned short shop_size = nd->u.scr.shop->items; + int w = 0; + int new_ = 0; + int64 z = 0; + int items[MAX_INVENTORY] = { 0 }; + + // process entries in buy list, one by one + for (int i = 0; i < VECTOR_LENGTH(*item_list); ++i) { + struct barter_itemlist_entry *entry = &VECTOR_INDEX(*item_list, i); + + if (entry->addAmount <= 0) + return 14; // not enough item amount in inventory + + const int j = entry->shopIndex; + if (j < 0 || j >= shop_size) + return 13; // no such item in shop + if (entry->addId != shop[j].nameid && entry->addId != itemdb_viewid(shop[j].nameid)) + return 13; // no such item in shop + entry->addId = shop[j].nameid; // item_avail replacement + if (!itemdb->exists(entry->addId)) + return 13; // item no longer in itemdb + + if ((int)shop[j].qty != -1 && entry->addAmount > (int)shop[j].qty) + return 14; // not enough item amount in shop + + int currencyCount = shop[j].value2; + for (int currencyIndex = 0; currencyIndex < currencyCount; currencyIndex ++) { + struct npc_barter_currency *currency = &shop[j].currency[currencyIndex]; + const int currencyItemId = currency->nameid; + const int currencyRefine = currency->refine; + int removeAmount = currency->amount * entry->addAmount; + if (removeAmount <= 0) + continue; + for (int n = 0; n < sd->status.inventorySize && removeAmount > 0; ++n) { + // check item id and existing amount + if (sd->status.inventory[n].nameid == currencyItemId && sd->status.inventory[n].amount > 0) { + // check item refine level + if (currencyRefine != -1 && sd->status.inventory[n].refine != currencyRefine) + continue; + if (sd->status.inventory[n].amount >= removeAmount) { + items[n] += removeAmount; + removeAmount = 0; + w -= itemdb_weight(currencyItemId) * removeAmount; + break; + } else { + items[n] += sd->status.inventory[n].amount; + removeAmount -= sd->status.inventory[n].amount; + w -= itemdb_weight(currencyItemId) * sd->status.inventory[n].amount; + } + } + if (items[n] > sd->status.inventory[n].amount) + return 14; // not enough item amount in inventory + } + if (removeAmount != 0) { + return 14; // not enough item amount in inventory + } + } + + entry->addId = shop[j].nameid; //item_avail replacement + + npc_market_qty[i] = j; + + if (!itemdb->isstackable(entry->addId) && entry->addAmount > 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 non-stackable item %d!\n", + sd->status.name, sd->status.account_id, sd->status.char_id, entry->addAmount, entry->addId); + entry->addAmount = 1; + } + + switch (pc->checkadditem(sd, entry->addId, entry->addAmount)) { + case ADDITEM_EXIST: + break; + case ADDITEM_NEW: + new_++; + break; + case ADDITEM_OVERAMOUNT: /* TODO find official response for this */ + return 1; + } + + z += (int64)shop[j].value * entry->addAmount; + w += itemdb_weight(entry->addId) * entry->addAmount; + } + + if (z > sd->status.zeny) + return 3; // Not enough Zeny + + if ((int64)w + sd->weight > sd->max_weight) + return 2; // Too heavy + + if (pc->inventoryblank(sd) < new_) + return 3; // Not enough space to store items + + for (int i = 0; i < sd->status.inventorySize; ++i) { + const int removeAmountTotal = items[i]; + if (removeAmountTotal == 0) + continue; + if (pc->delitem(sd, i, removeAmountTotal, 0, DELITEM_SOLD, LOG_TYPE_NPC) != 0) { + return 11; // unknown exploit + } + } + + pc->payzeny(sd, (int)z, LOG_TYPE_NPC, NULL); + + for (int i = 0; i < VECTOR_LENGTH(*item_list); ++i) { + struct barter_itemlist_entry *entry = &VECTOR_INDEX(*item_list, i); + const int shopIdx = npc_market_qty[i]; + + if ((int)shop[shopIdx].qty != -1) { + if (entry->addAmount > (int)shop[shopIdx].qty) /* wohoo someone tampered with the packet. */ + return 14; + shop[shopIdx].qty -= entry->addAmount; + } + + npc->expanded_barter_tosql(nd, shopIdx); + + if (itemdb_type(entry->addId) == IT_PETEGG) { + pet->create_egg(sd, entry->addId); + } else { + struct item item_tmp; + memset(&item_tmp, 0, sizeof(item_tmp)); + item_tmp.nameid = entry->addId; + item_tmp.identify = 1; + pc->additem(sd, &item_tmp, entry->addAmount, LOG_TYPE_NPC); + } + } + + return 12; } /// npc_selllist for script-controlled shops -int npc_selllist_sub(struct map_session_data *sd, struct itemlist *item_list, struct npc_data *nd) +static int npc_selllist_sub(struct map_session_data *sd, struct itemlist *item_list, struct npc_data *nd) { char npc_ev[EVENT_NAME_LENGTH]; char card_slot[NAME_LENGTH]; + char opt_index_str[NAME_LENGTH]; + char opt_value_str[NAME_LENGTH]; int i, j; int key_nameid = 0; int key_amount = 0; @@ -2107,6 +2719,8 @@ int npc_selllist_sub(struct map_session_data *sd, struct itemlist *item_list, st int key_attribute = 0; int key_identify = 0; int key_card[MAX_SLOTS]; + int key_opt_idx[MAX_ITEM_OPTIONS]; + int key_opt_value[MAX_ITEM_OPTIONS]; nullpo_ret(sd); nullpo_ret(item_list); @@ -2126,6 +2740,17 @@ int npc_selllist_sub(struct map_session_data *sd, struct itemlist *item_list, st script->cleararray_pc(sd, card_slot, (void*)0); } + for (j = 0; j < MAX_ITEM_OPTIONS; j++) { // Clear Each item option entry + key_opt_idx[j] = 0; + key_opt_value[j] = 0; + + snprintf(opt_index_str, sizeof(opt_index_str), "@slot_opt_idx%d", j + 1); + script->cleararray_pc(sd, opt_index_str, (void*)0); + + snprintf(opt_value_str, sizeof(opt_value_str), "@slot_opt_val%d", j + 1); + script->cleararray_pc(sd, opt_value_str, (void*)0); + } + // save list of to be sold items for (i = 0; i < VECTOR_LENGTH(*item_list); i++) { struct itemlist_entry *entry = &VECTOR_INDEX(*item_list, i); @@ -2151,6 +2776,17 @@ int npc_selllist_sub(struct map_session_data *sd, struct itemlist *item_list, st script->setarray_pc(sd, card_slot, i, (void*)card, &key_card[j]); } + for (j = 0; j < MAX_ITEM_OPTIONS; j++) { + intptr_t opt_idx = item->option[j].index; + intptr_t opt_value = item->option[j].value; + + snprintf(opt_index_str, sizeof(opt_index_str), "@slot_opt_idx%d", j + 1); + script->setarray_pc(sd, opt_index_str, i, (void*)opt_idx, &key_opt_idx[j]); + + snprintf(opt_value_str, sizeof(opt_value_str), "@slot_opt_val%d", j + 1); + script->setarray_pc(sd, opt_value_str, i, (void*)opt_value, &key_opt_value[j]); + } + } // invoke event @@ -2163,7 +2799,7 @@ int npc_selllist_sub(struct map_session_data *sd, struct itemlist *item_list, st /// /// @param item_list 'n' pairs <index,amount> /// @return result code for clif->parse_NpcSellListSend -int npc_selllist(struct map_session_data *sd, struct itemlist *item_list) +static int npc_selllist(struct map_session_data *sd, struct itemlist *item_list) { int64 z; int i,skill_t, skill_idx = skill->get_index(MC_OVERCHARGE); @@ -2178,18 +2814,21 @@ int npc_selllist(struct map_session_data *sd, struct itemlist *item_list) } if( nd->subtype != SHOP ) { - if( !(nd->subtype == SCRIPT && nd->u.scr.shop && nd->u.scr.shop->type == NST_ZENY) ) + if (!(nd->subtype == SCRIPT && nd->u.scr.shop && (nd->u.scr.shop->type == NST_ZENY || nd->u.scr.shop->type == NST_MARKET))) return 1; } z = 0; + if (sd->status.zeny >= MAX_ZENY && nd->master_nd == NULL) + return 1; + // verify the sell list for (i = 0; i < VECTOR_LENGTH(*item_list); i++) { struct itemlist_entry *entry = &VECTOR_INDEX(*item_list, i); int nameid, value, idx = entry->id; - if (idx >= MAX_INVENTORY || idx < 0 || entry->amount < 0) { + if (idx >= sd->status.inventorySize || idx < 0 || entry->amount < 0) { return 1; } @@ -2230,10 +2869,17 @@ int npc_selllist(struct map_session_data *sd, struct itemlist *item_list) } } + // Achievements [Smokexyz/Hercules] + achievement->validate_item_sell(sd, sd->status.inventory[idx].nameid, entry->amount); + pc->delitem(sd, idx, entry->amount, 0, DELITEM_SOLD, LOG_TYPE_NPC); + } - if( z > MAX_ZENY ) + if (z + sd->status.zeny > MAX_ZENY && nd->master_nd == NULL) + return 1; + + if (z > MAX_ZENY) z = MAX_ZENY; pc->getzeny(sd, (int)z, LOG_TYPE_NPC, NULL); @@ -2256,7 +2902,8 @@ int npc_selllist(struct map_session_data *sd, struct itemlist *item_list) //Atempt to remove an npc from a map //This doesn't remove it from map_db -int npc_remove_map(struct npc_data* nd) { +static int npc_remove_map(struct npc_data *nd) +{ int16 m,i; nullpo_retr(1, nd); @@ -2279,7 +2926,7 @@ int npc_remove_map(struct npc_data* nd) { /** * @see DBApply */ -int npc_unload_ev(union DBKey key, struct DBData *data, va_list ap) +static int npc_unload_ev(union DBKey key, struct DBData *data, va_list ap) { struct event_data* ev = DB->data2ptr(data); char* npcname = va_arg(ap, char *); @@ -2294,7 +2941,7 @@ int npc_unload_ev(union DBKey key, struct DBData *data, va_list ap) /** * @see DBApply */ -int npc_unload_ev_label(union DBKey key, struct DBData *data, va_list ap) +static int npc_unload_ev_label(union DBKey key, struct DBData *data, va_list ap) { struct linkdb_node **label_linkdb = DB->data2ptr(data); struct npc_data* nd = va_arg(ap, struct npc_data *); @@ -2304,119 +2951,193 @@ int npc_unload_ev_label(union DBKey key, struct DBData *data, va_list ap) return 0; } -//Chk if npc matches src_id, then unload. -//Sub-function used to find duplicates. -int npc_unload_dup_sub(struct npc_data* nd, va_list args) +/** + * Unloads a NPC if it's a duplicate of the passed one. + * + * @param nd The NPC to check. + * @param args List of arguments. + * @return Always 0. + * + **/ +static int npc_unload_dup_sub(struct npc_data *nd, va_list args) { - int src_id; - nullpo_ret(nd); - src_id = va_arg(args, int); + + const int src_id = va_arg(args, int); + const int unload_mobs = va_arg(args, int); + if (nd->src_id == src_id) - npc->unload(nd, true); + npc->unload(nd, true, (unload_mobs == 1)); + return 0; } -//Removes all npcs that are duplicates of the passed one. [Skotlex] -void npc_unload_duplicates(struct npc_data* nd) { +/** + * Unloads all NPCs which are duplicates of the passed one. + * + * @param nd The source NPC. + * @param unload_mobs If true, mobs spawned by duplicates will be removed. + * + * @author Skotlex + * + **/ +static void npc_unload_duplicates(struct npc_data *nd, bool unload_mobs) +{ nullpo_retv(nd); - map->foreachnpc(npc->unload_dup_sub,nd->bl.id); + + map->foreachnpc(npc->unload_dup_sub, nd->bl.id, unload_mobs); +} + +/** + * Removes a mob, which was spawned by a NPC (monster/areamonster/guardian/bg_monster/atcommand("@monster xy")). + * + * @param md The mob to remove. + * @param args List of arguments. + * @return 1 on success, 0 on failure. + * + * @author Kenpachi + * + **/ +static int npc_unload_mob(struct mob_data *md, va_list args) +{ + nullpo_ret(md); + + const int npc_id = va_arg(args, int); + + if (md->npc_id == npc_id) { + md->state.npc_killmonster = 1; + status_kill(&md->bl); + return 1; + } + + return 0; } -//Removes an npc from map and db. -//Single is to free name (for duplicates). -int npc_unload(struct npc_data* nd, bool single) +/** + * Removes a NPC from map and database. + * + * @param nd The NPC which should be removed. + * @param single If true, names are freed. (For duplicates.) + * @param unload_mobs If true, mobs spawned by the NPC will be removed. + * @return Always 0. + * + **/ +static int npc_unload(struct npc_data *nd, bool single, bool unload_mobs) { nullpo_ret(nd); - if( nd->ud && nd->ud != &npc->base_ud ) { + if (nd->ud != NULL && nd->ud != &npc->base_ud) skill->clear_unitgroup(&nd->bl); - } npc->remove_map(nd); map->deliddb(&nd->bl); - if( single ) + + if (single) strdb_remove(npc->name_db, nd->exname); - if (nd->chat_id) // remove npc chatroom object and kick users + if (nd->chat_id != 0) /// Remove NPC chatroom object and kick users. chat->delete_npc_chat(nd); - npc_chat->finalize(nd); // deallocate npc PCRE data structures + npc_chat->finalize(nd); /// Deallocate NPC PCRE data structures. if (single && nd->path != NULL) { npc->releasepathreference(nd->path); nd->path = NULL; } - if( single && nd->bl.m != -1 ) - map->remove_questinfo(nd->bl.m,nd); + if (single && nd->bl.m != INDEX_NOT_FOUND) + map->remove_questinfo(nd->bl.m, nd); + + npc->questinfo_clear(nd); - if (nd->src_id == 0 && ( nd->subtype == SHOP || nd->subtype == CASHSHOP)) { - //src check for duplicate shops [Orcao] - aFree(nd->u.shop.shop_item); + if (nd->src_id == 0 && (nd->subtype == SHOP || nd->subtype == CASHSHOP)) { + aFree(nd->u.shop.shop_item); /// src check for duplicate shops. [Orcao] } else if (nd->subtype == SCRIPT) { - struct s_mapiterator *iter; - struct map_session_data *sd = NULL; + char evname[EVENT_NAME_LENGTH]; + + snprintf(evname, ARRAYLENGTH(evname), "%s::OnNPCUnload", nd->exname); + + struct event_data *ev = strdb_get(npc->ev_db, evname); + + if (ev != NULL) + script->run_npc(nd->u.scr.script, ev->pos, 0, nd->bl.id); /// Run OnNPCUnload. - if( single ) { - npc->ev_db->foreach(npc->ev_db,npc->unload_ev,nd->exname); //Clean up all events related - npc->ev_label_db->foreach(npc->ev_label_db,npc->unload_ev_label,nd); + if (single) { + npc->ev_db->foreach(npc->ev_db, npc->unload_ev, nd->exname); /// Clean up all related events. + npc->ev_label_db->foreach(npc->ev_label_db, npc->unload_ev_label, nd); } - iter = mapit_geteachpc(); - for (sd = BL_UCAST(BL_PC, mapit->first(iter)); mapit->exists(iter); sd = BL_UCAST(BL_PC, mapit->next(iter))) { - if (sd->npc_timer_id != INVALID_TIMER ) { + struct s_mapiterator *iter = mapit_geteachpc(); + struct map_session_data *sd = BL_UCAST(BL_PC, mapit->first(iter)); + + for (; mapit->exists(iter); sd = BL_UCAST(BL_PC, mapit->next(iter))) { + if (sd->npc_timer_id != INVALID_TIMER) { const struct TimerData *td = timer->get(sd->npc_timer_id); - if( td && td->id != nd->bl.id ) + if (td != NULL && td->id != nd->bl.id) continue; - if( td && td->data ) + if (td != NULL && td->data != 0) ers_free(npc->timer_event_ers, (void*)td->data); + timer->delete(sd->npc_timer_id, npc->timerevent); sd->npc_timer_id = INVALID_TIMER; } } + mapit->free(iter); if (nd->u.scr.timerid != INVALID_TIMER) { - const struct TimerData *td; - td = timer->get(nd->u.scr.timerid); - if (td && td->data) + const struct TimerData *td = timer->get(nd->u.scr.timerid); + + if (td != NULL && td->data != 0) ers_free(npc->timer_event_ers, (void*)td->data); + timer->delete(nd->u.scr.timerid, npc->timerevent); } - if (nd->u.scr.timer_event) + + if (nd->u.scr.timer_event != NULL) aFree(nd->u.scr.timer_event); + if (nd->src_id == 0) { - if(nd->u.scr.script) { + if (nd->u.scr.script != NULL) { script->free_code(nd->u.scr.script); nd->u.scr.script = NULL; } - if (nd->u.scr.label_list) { + + if (nd->u.scr.label_list != NULL) { aFree(nd->u.scr.label_list); nd->u.scr.label_list = NULL; nd->u.scr.label_list_num = 0; } - if(nd->u.scr.shop) { - if(nd->u.scr.shop->item) + + if (nd->u.scr.shop != NULL) { + if (nd->u.scr.shop->item != NULL) { + for (int i = 0; i < nd->u.scr.shop->items; i ++) { + if (nd->u.scr.shop->item[i].currency != NULL) + aFree(nd->u.scr.shop->item[i].currency); + } aFree(nd->u.scr.shop->item); + } + aFree(nd->u.scr.shop); } } - if( nd->u.scr.guild_id ) + + if (nd->u.scr.guild_id > 0) guild->flag_remove(nd); } - if( nd->ud && nd->ud != &npc->base_ud ) { + if (nd->ud != NULL && nd->ud != &npc->base_ud) { aFree(nd->ud); nd->ud = NULL; } - HPM->data_store_destroy(&nd->hdata); + if (unload_mobs) + map->foreachmob(npc->unload_mob, nd->bl.id); + HPM->data_store_destroy(&nd->hdata); aFree(nd); - return 0; } @@ -2425,7 +3146,7 @@ int npc_unload(struct npc_data* nd, bool single) // /// Clears the npc source file list -void npc_clearsrcfile(void) +static void npc_clearsrcfile(void) { struct npc_src_list* file = npc->src_files; @@ -2437,24 +3158,22 @@ void npc_clearsrcfile(void) npc->src_files = NULL; } -/// Adds a npc source file (or removes all) -void npc_addsrcfile(const char* name) +/** + * Adds a npc source file. + * + * @param name The file name to add. + */ +static void npc_addsrcfile(const char *name) { struct npc_src_list* file; struct npc_src_list* file_prev = NULL; nullpo_retv(name); - if( strcmpi(name, "clear") == 0 ) - { - npc->clearsrcfile(); - return; - } // prevent multiple insert of source files file = npc->src_files; - while( file != NULL ) - { - if( strcmp(name, file->name) == 0 ) + while (file != NULL) { + if (strcmp(name, file->name) == 0) return;// found the file, no need to insert it again file_prev = file; file = file->next; @@ -2469,24 +3188,21 @@ void npc_addsrcfile(const char* name) file_prev->next = file; } -/// Removes a npc source file (or all) -void npc_delsrcfile(const char* name) +/** + * Removes a npc source file. + * + * @param name The file name to remove. + */ +static void npc_delsrcfile(const char *name) { struct npc_src_list* file = npc->src_files; struct npc_src_list* file_prev = NULL; nullpo_retv(name); - if( strcmpi(name, "all") == 0 ) - { - npc->clearsrcfile(); - return; - } - while( file != NULL ) - { - if( strcmp(file->name, name) == 0 ) - { - if( npc->src_files == file ) + while (file != NULL) { + if (strcmp(file->name, name) == 0) { + if (npc->src_files == file) npc->src_files = file->next; else file_prev->next = file->next; @@ -2504,15 +3220,15 @@ void npc_delsrcfile(const char* name) * @param filepath The file path to retain. * @return A retained reference to filepath. */ -const char *npc_retainpathreference(const char *filepath) +static const char *npc_retainpathreference(const char *filepath) { struct npc_path_data * npd = NULL; nullpo_ret(filepath); - if (npc_last_path == filepath) { - if (npc_last_npd != NULL) - npc_last_npd->references++; - return npc_last_ref; + if (npc->npc_last_path == filepath) { + if (npc->npc_last_npd != NULL) + npc->npc_last_npd->references++; + return npc->npc_last_ref; } if ((npd = strdb_get(npc->path_db,filepath)) == NULL) { @@ -2527,9 +3243,9 @@ const char *npc_retainpathreference(const char *filepath) npd->references++; - npc_last_npd = npd; - npc_last_ref = npd->path; - npc_last_path = filepath; + npc->npc_last_npd = npd; + npc->npc_last_ref = npd->path; + npc->npc_last_path = filepath; return npd->path; } @@ -2539,13 +3255,13 @@ const char *npc_retainpathreference(const char *filepath) * * @param filepath The file path to release. */ -void npc_releasepathreference(const char *filepath) +static void npc_releasepathreference(const char *filepath) { struct npc_path_data* npd = NULL; nullpo_retv(filepath); - if (filepath != npc_last_ref) { + if (filepath != npc->npc_last_ref) { npd = strdb_get(npc->path_db, filepath); } @@ -2558,7 +3274,8 @@ void npc_releasepathreference(const char *filepath) /// Parses and sets the name and exname of a npc. /// Assumes that m, x and y are already set in nd. -void npc_parsename(struct npc_data* nd, const char* name, const char* start, const char* buffer, const char* filepath) { +static void npc_parsename(struct npc_data *nd, const char *name, const char *start, const char *buffer, const char *filepath) +{ const char* p; struct npc_data* dnd;// duplicate npc char newname[NAME_LENGTH]; @@ -2601,7 +3318,7 @@ void npc_parsename(struct npc_data* nd, const char* name, const char* start, con do { ++i; - snprintf(newname, ARRAYLENGTH(newname), "%d_%d_%d_%d", i, nd->bl.m, nd->bl.x, nd->bl.y); + safesnprintf(newname, ARRAYLENGTH(newname), "%d_%d_%d_%d", i, nd->bl.m, nd->bl.x, nd->bl.y); } while( npc->name2id(newname) != NULL ); strcpy(this_mapname, (nd->bl.m == -1 ? "(not on a map)" : mapindex_id2name(map_id2index(nd->bl.m)))); @@ -2616,7 +3333,8 @@ void npc_parsename(struct npc_data* nd, const char* name, const char* start, con // Parse View // Support for using Constants in place of NPC View IDs. -int npc_parseview(const char* w4, const char* start, const char* buffer, const char* filepath) { +static int npc_parseview(const char *w4, const char *start, const char *buffer, const char *filepath) +{ int val = FAKE_NPC, i = 0; char viewid[1024]; // Max size of name from constants.conf, see script->read_constdb. @@ -2650,7 +3368,7 @@ int npc_parseview(const char* w4, const char* start, const char* buffer, const c // View is ID // Checks if given view is an ID or constant. -bool npc_viewisid(const char * viewid) +static bool npc_viewisid(const char *viewid) { nullpo_retr(false, viewid); if (atoi(viewid) != FAKE_NPC) { @@ -2674,7 +3392,7 @@ bool npc_viewisid(const char * viewid) * @param class_ The NPC view class. * @return A pointer to the created NPC data (ownership passed to the caller). */ -struct npc_data *npc_create_npc(enum npc_subtype subtype, int m, int x, int y, uint8 dir, int16 class_) +static struct npc_data *npc_create_npc(enum npc_subtype subtype, int m, int x, int y, enum unit_dir dir, int class_) { struct npc_data *nd; @@ -2690,12 +3408,15 @@ struct npc_data *npc_create_npc(enum npc_subtype subtype, int m, int x, int y, u nd->area_size = AREA_SIZE + 1; nd->class_ = class_; nd->speed = 200; + nd->vd = npc_viewdb[0]; // Copy INVISIBLE_CLASS view data. Actual view data is set by npc->add_to_location() later. + VECTOR_INIT(nd->qi_data); return nd; } //Add then display an npc warp on map -struct npc_data* npc_add_warp(char* name, short from_mapid, short from_x, short from_y, short xs, short ys, unsigned short to_mapindex, short to_x, short to_y) { +static struct npc_data *npc_add_warp(char *name, short from_mapid, short from_x, short from_y, short xs, short ys, unsigned short to_mapindex, short to_x, short to_y) +{ int i, flag = 0; struct npc_data *nd; @@ -2708,10 +3429,10 @@ struct npc_data* npc_add_warp(char* name, short from_mapid, short from_x, short flag = 1; if (flag == 1) - snprintf(nd->exname, ARRAYLENGTH(nd->exname), "warp_%d_%d_%d", from_mapid, from_x, from_y); + safesnprintf(nd->exname, ARRAYLENGTH(nd->exname), "warp_%d_%d_%d", from_mapid, from_x, from_y); for( i = 0; npc->name2id(nd->exname) != NULL; ++i ) - snprintf(nd->exname, ARRAYLENGTH(nd->exname), "warp%d_%d_%d_%d", i, from_mapid, from_x, from_y); + safesnprintf(nd->exname, ARRAYLENGTH(nd->exname), "warp%d_%d_%d_%d", i, from_mapid, from_x, from_y); safestrncpy(nd->name, nd->exname, ARRAYLENGTH(nd->name)); nd->u.warp.mapindex = to_mapindex; @@ -2742,7 +3463,7 @@ struct npc_data* npc_add_warp(char* name, short from_mapid, short from_x, short * (EXIT_FAILURE) status. May be NULL. * @return A pointer to the advanced buffer position. */ -const char *npc_parse_warp(const char *w1, const char *w2, const char *w3, const char *w4, const char *start, const char *buffer, const char *filepath, int *retval) +static const char *npc_parse_warp(const char *w1, const char *w2, const char *w3, const char *w4, const char *start, const char *buffer, const char *filepath, int *retval) { int x, y, xs, ys, to_x, to_y, m; unsigned short i; @@ -2785,7 +3506,7 @@ const char *npc_parse_warp(const char *w1, const char *w2, const char *w3, const nd->u.warp.y = to_y; nd->u.warp.xs = xs; nd->u.warp.ys = ys; - npc_warp++; + npc->npc_warp++; npc->add_to_location(nd); @@ -2809,7 +3530,7 @@ const char *npc_parse_warp(const char *w1, const char *w2, const char *w3, const * (EXIT_FAILURE) status. May be NULL. * @return A pointer to the advanced buffer position. */ -const char *npc_parse_shop(const char *w1, const char *w2, const char *w3, const char *w4, const char *start, const char *buffer, const char *filepath, int *retval) +static const char *npc_parse_shop(const char *w1, const char *w2, const char *w3, const char *w4, const char *start, const char *buffer, const char *filepath, int *retval) { //TODO: could be rewritten to NOT need this temp array [ultramage] // We could use nd->u.shop.shop_item to store directly the items, but this could lead @@ -2818,7 +3539,7 @@ const char *npc_parse_shop(const char *w1, const char *w2, const char *w3, const struct npc_item_list *items = NULL; size_t items_count = 40; // Starting items size - char *p; + const char *p; int x, y, dir, m, i, class_; struct npc_data *nd; enum npc_subtype type; @@ -2930,13 +3651,14 @@ const char *npc_parse_shop(const char *w1, const char *w2, const char *w3, const npc->parsename(nd, w3, start, buffer, filepath); nd->path = npc->retainpathreference(filepath); - ++npc_shop; + ++npc->npc_shop; npc->add_to_location(nd); return strchr(start,'\n');// continue } -void npc_convertlabel_db(struct npc_label_list* label_list, const char *filepath) { +static void npc_convertlabel_db(struct npc_label_list *label_list, const char *filepath) +{ int i; nullpo_retv(label_list); @@ -2968,7 +3690,8 @@ void npc_convertlabel_db(struct npc_label_list* label_list, const char *filepath } // Skip the contents of a script. -const char* npc_skip_script(const char* start, const char* buffer, const char* filepath, int *retval) { +static const char *npc_skip_script(const char *start, const char *buffer, const char *filepath, int *retval) +{ const char* p; int curly_count; @@ -3054,7 +3777,7 @@ const char* npc_skip_script(const char* start, const char* buffer, const char* f * (EXIT_FAILURE) status. May be NULL. * @return A pointer to the advanced buffer position. */ -const char *npc_parse_script(const char *w1, const char *w2, const char *w3, const char *w4, const char *start, const char *buffer, const char *filepath, int options, int *retval) +static const char *npc_parse_script(const char *w1, const char *w2, const char *w3, const char *w4, const char *start, const char *buffer, const char *filepath, int options, int *retval) { int x, y, dir = 0, m, xs = 0, ys = 0; // [Valaris] thanks to fov struct script_code *scriptroot; @@ -3136,7 +3859,7 @@ const char *npc_parse_script(const char *w1, const char *w2, const char *w3, con if( options&NPO_TRADER ) nd->u.scr.trader = true; nd->u.scr.shop = NULL; - ++npc_script; + ++npc->npc_script; npc->add_to_location(nd); //----------------------------------------- @@ -3174,7 +3897,7 @@ const char *npc_parse_script(const char *w1, const char *w2, const char *w3, con * * @param nd The NPC to register. */ -void npc_add_to_location(struct npc_data *nd) +static void npc_add_to_location(struct npc_data *nd) { nullpo_retv(nd); @@ -3198,7 +3921,7 @@ void npc_add_to_location(struct npc_data *nd) /** * Duplicates a script (@see npc_duplicate_sub) */ -bool npc_duplicate_script_sub(struct npc_data *nd, const struct npc_data *snd, int xs, int ys, int options) +static bool npc_duplicate_script_sub(struct npc_data *nd, const struct npc_data *snd, int xs, int ys, int options) { int i; bool retval = true; @@ -3206,7 +3929,7 @@ bool npc_duplicate_script_sub(struct npc_data *nd, const struct npc_data *snd, i nullpo_retr(false, nd); nullpo_retr(false, snd); - ++npc_script; + ++npc->npc_script; nd->u.scr.xs = xs; nd->u.scr.ys = ys; nd->u.scr.script = snd->u.scr.script; @@ -3248,12 +3971,12 @@ bool npc_duplicate_script_sub(struct npc_data *nd, const struct npc_data *snd, i /** * Duplicates a shop or cash shop (@see npc_duplicate_sub) */ -bool npc_duplicate_shop_sub(struct npc_data *nd, const struct npc_data *snd, int xs, int ys, int options) +static bool npc_duplicate_shop_sub(struct npc_data *nd, const struct npc_data *snd, int xs, int ys, int options) { nullpo_retr(false, nd); nullpo_retr(false, snd); - ++npc_shop; + ++npc->npc_shop; nd->u.shop.shop_item = snd->u.shop.shop_item; nd->u.shop.count = snd->u.shop.count; @@ -3266,12 +3989,12 @@ bool npc_duplicate_shop_sub(struct npc_data *nd, const struct npc_data *snd, int /** * Duplicates a warp (@see npc_duplicate_sub) */ -bool npc_duplicate_warp_sub(struct npc_data *nd, const struct npc_data *snd, int xs, int ys, int options) +static bool npc_duplicate_warp_sub(struct npc_data *nd, const struct npc_data *snd, int xs, int ys, int options) { nullpo_retr(false, nd); nullpo_retr(false, snd); - ++npc_warp; + ++npc->npc_warp; nd->u.warp.xs = xs; nd->u.warp.ys = ys; nd->u.warp.mapindex = snd->u.warp.mapindex; @@ -3298,7 +4021,7 @@ bool npc_duplicate_warp_sub(struct npc_data *nd, const struct npc_data *snd, int * @param options The NPC options. * @retval false if there were any issues while creating and validating the NPC. */ -bool npc_duplicate_sub(struct npc_data *nd, const struct npc_data *snd, int xs, int ys, int options) +static bool npc_duplicate_sub(struct npc_data *nd, const struct npc_data *snd, int xs, int ys, int options) { nullpo_retr(false, nd); nullpo_retr(false, snd); @@ -3356,7 +4079,7 @@ bool npc_duplicate_sub(struct npc_data *nd, const struct npc_data *snd, int xs, * @remark * Only `NPO_ONINIT` is available trough the options argument. */ -const char *npc_parse_duplicate(const char *w1, const char *w2, const char *w3, const char *w4, const char *start, const char *buffer, const char *filepath, int options, int *retval) +static const char *npc_parse_duplicate(const char *w1, const char *w2, const char *w3, const char *w4, const char *start, const char *buffer, const char *filepath, int options, int *retval) { int x, y, dir, m, xs = -1, ys = -1; char srcname[128]; @@ -3448,7 +4171,7 @@ const char *npc_parse_duplicate(const char *w1, const char *w2, const char *w3, * @return success state. * @retval 0 in case of successful creation. */ -int npc_duplicate4instance(struct npc_data *snd, int16 m) +static int npc_duplicate4instance(struct npc_data *snd, int16 m) { char newname[NAME_LENGTH]; int dm = -1, im = -1, xs = -1, ys = -1; @@ -3504,7 +4227,7 @@ int npc_duplicate4instance(struct npc_data *snd, int16 m) } //Set mapcell CELL_NPC to trigger event later -void npc_setcells(struct npc_data* nd) +static void npc_setcells(struct npc_data *nd) { int16 m, x, y, xs, ys; int i,j; @@ -3538,7 +4261,7 @@ void npc_setcells(struct npc_data* nd) } } -int npc_unsetcells_sub(struct block_list *bl, va_list ap) +static int npc_unsetcells_sub(struct block_list *bl, va_list ap) { struct npc_data *nd = NULL; int id = va_arg(ap, int); @@ -3553,7 +4276,8 @@ int npc_unsetcells_sub(struct block_list *bl, va_list ap) return 1; } -void npc_unsetcells(struct npc_data* nd) { +static void npc_unsetcells(struct npc_data *nd) +{ int16 m, x, y, xs, ys; int i,j, x0, x1, y0, y1; @@ -3593,7 +4317,7 @@ void npc_unsetcells(struct npc_data* nd) { map->foreachinarea( npc->unsetcells_sub, m, x0, y0, x1, y1, BL_NPC, nd->bl.id ); } -void npc_movenpc(struct npc_data* nd, int16 x, int16 y) +static void npc_movenpc(struct npc_data *nd, int16 x, int16 y) { int16 m; nullpo_retv(nd); @@ -3612,21 +4336,22 @@ void npc_movenpc(struct npc_data* nd, int16 x, int16 y) /// /// @param nd Target npc /// @param newname New display name -void npc_setdisplayname(struct npc_data* nd, const char* newname) +static void npc_setdisplayname(struct npc_data *nd, const char *newname) { nullpo_retv(nd); nullpo_retv(newname); safestrncpy(nd->name, newname, sizeof(nd->name)); if( map->list[nd->bl.m].users ) - clif->charnameack(0, &nd->bl); + clif->blname_ack(0, &nd->bl); } /// Changes the display class of the npc. /// /// @param nd Target npc /// @param class_ New display class -void npc_setclass(struct npc_data* nd, short class_) { +static void npc_setclass(struct npc_data *nd, int class_) +{ nullpo_retv(nd); if( nd->class_ == class_ ) @@ -3640,8 +4365,20 @@ void npc_setclass(struct npc_data* nd, short class_) { clif->spawn(&nd->bl);// fade in } +static void npc_refresh(struct npc_data *nd) +{ + nullpo_retv(nd); + + if (map->list[nd->bl.m].users) { + // using here CLR_TRICKDEAD because other flags show effects. + // probably need use other flag or other way to refresh npc. + clif->clearunit_area(&nd->bl, CLR_TRICKDEAD); // fade out + clif->spawn(&nd->bl); // fade in + } +} + // @commands (script based) -int npc_do_atcmd_event(struct map_session_data* sd, const char* command, const char* message, const char* eventname) +static int npc_do_atcmd_event(struct map_session_data *sd, const char *command, const char *message, const char *eventname) { struct event_data* ev = (struct event_data*)strdb_get(npc->ev_db, eventname); struct npc_data *nd; @@ -3728,7 +4465,7 @@ int npc_do_atcmd_event(struct map_session_data* sd, const char* command, const c * (EXIT_FAILURE) status. May be NULL. * @return A pointer to the advanced buffer position. */ -const char *npc_parse_function(const char *w1, const char *w2, const char *w3, const char *w4, const char *start, const char *buffer, const char *filepath, int *retval) +static const char *npc_parse_function(const char *w1, const char *w2, const char *w3, const char *w4, const char *start, const char *buffer, const char *filepath, int *retval) { struct DBMap *func_db; struct DBData old_data; @@ -3770,25 +4507,30 @@ const char *npc_parse_function(const char *w1, const char *w2, const char *w3, c struct script_code *oldscript = (struct script_code*)DB->data2ptr(&old_data); ShowWarning("npc_parse_function: Overwriting user function [%s] in file '%s', line '%d'.\n", w3, filepath, strline(buffer,start-buffer)); script->free_vars(oldscript->local.vars); - aFree(oldscript->script_buf); + VECTOR_CLEAR(oldscript->script_buf); aFree(oldscript); } return end; } -/*========================================== - * Parse Mob 1 - Parse mob list into each map - * Parse Mob 2 - Actually Spawns Mob - * [Wizputer] - *------------------------------------------*/ -void npc_parse_mob2(struct spawn_data* mobspawn) +/** + * Spawns a mob by using the passed spawn data. (Permanent mob spawns.) + * npc_parse_mob() - Parses mob list into each map. + * npc_parse_mob2() - Actually spawns mob. + * + * @param mobspawn The mobs spawn data. + * + * @author Wizputer + * + **/ +static void npc_parse_mob2(struct spawn_data *mobspawn) { - int i; - nullpo_retv(mobspawn); - for( i = mobspawn->active; i < mobspawn->num; ++i ) { - struct mob_data* md = mob->spawn_dataset(mobspawn); + + for (int i = mobspawn->active; i < mobspawn->num; ++i) { + struct mob_data *md = mob->spawn_dataset(mobspawn, 0); + md->spawn = mobspawn; md->spawn->active++; mob->spawn(md); @@ -3812,7 +4554,7 @@ void npc_parse_mob2(struct spawn_data* mobspawn) * (EXIT_FAILURE) status. May be NULL. * @return A pointer to the advanced buffer position. */ -const char *npc_parse_mob(const char *w1, const char *w2, const char *w3, const char *w4, const char *start, const char *buffer, const char *filepath, int *retval) +static const char *npc_parse_mob(const char *w1, const char *w2, const char *w3, const char *w4, const char *start, const char *buffer, const char *filepath, int *retval) { int num, class_, m,x,y,xs,ys, i,j; int mob_lv = -1, ai = -1, size = -1; @@ -3827,7 +4569,12 @@ const char *npc_parse_mob(const char *w1, const char *w2, const char *w3, const memset(&mobspawn, 0, sizeof(struct spawn_data)); - mobspawn.state.boss = (strcmp(w2,"boss_monster") == 0 ? 1 : 0); + if (strcmp(w2, "boss_monster") == 0) + mobspawn.state.boss = BTYPE_MVP; + else if (strcmp(w2, "miniboss_monster") == 0) + mobspawn.state.boss = BTYPE_BOSS; + else + mobspawn.state.boss = BTYPE_NONE; // w1=<map name>,<x>,<y>,<xs>,<ys> // w3=<mob name>{,<mob level>} @@ -3889,7 +4636,7 @@ const char *npc_parse_mob(const char *w1, const char *w2, const char *w3, const mobspawn.num = (unsigned short)num; mobspawn.active = 0; - mobspawn.class_ = (short) class_; + mobspawn.class_ = class_; mobspawn.x = (unsigned short)x; mobspawn.y = (unsigned short)y; mobspawn.xs = (signed short)xs; @@ -3919,10 +4666,10 @@ const char *npc_parse_mob(const char *w1, const char *w2, const char *w3, const } //Use db names instead of the spawn file ones. - if(battle_config.override_mob_names==1) - strcpy(mobspawn.name,"--en--"); - else if (battle_config.override_mob_names==2) - strcpy(mobspawn.name,"--ja--"); + if (battle_config.override_mob_names == 1) + strcpy(mobspawn.name, DEFAULT_MOB_NAME); + else if (battle_config.override_mob_names == 2) + strcpy(mobspawn.name, DEFAULT_MOB_JNAME); else safestrncpy(mobspawn.name, mobname, sizeof(mobspawn.name)); @@ -3967,7 +4714,7 @@ const char *npc_parse_mob(const char *w1, const char *w2, const char *w3, const // spawn / cache the new mobs if( battle_config.dynamic_mobs && map->addmobtolist(data->m, data) >= 0 ) { data->state.dynamic = true; - npc_cache_mob += data->num; + npc->npc_cache_mob += data->num; // check if target map has players // (usually shouldn't occur when map server is just starting, @@ -3978,10 +4725,10 @@ const char *npc_parse_mob(const char *w1, const char *w2, const char *w3, const } else { data->state.dynamic = false; npc->parse_mob2(data); - npc_delay_mob += data->num; + npc->npc_delay_mob += data->num; } - npc_mob++; + npc->npc_mob++; return strchr(start,'\n');// continue } @@ -3993,7 +4740,7 @@ const char *npc_parse_mob(const char *w1, const char *w2, const char *w3, const * * @see npc_parse_mapflag */ -void npc_parse_unknown_mapflag(const char *name, const char *w3, const char *w4, const char* start, const char* buffer, const char* filepath, int *retval) +static void npc_parse_unknown_mapflag(const char *name, const char *w3, const char *w4, const char *start, const char *buffer, const char *filepath, int *retval) { ShowError("npc_parse_mapflag: unrecognized mapflag '%s' in file '%s', line '%d'.\n", w3, filepath, strline(buffer,start-buffer)); if (retval) @@ -4025,7 +4772,7 @@ void npc_parse_unknown_mapflag(const char *name, const char *w3, const char *w4, * (EXIT_FAILURE) status. May be NULL. * @return A pointer to the advanced buffer position. */ -const char *npc_parse_mapflag(const char *w1, const char *w2, const char *w3, const char *w4, const char *start, const char *buffer, const char *filepath, int *retval) +static const char *npc_parse_mapflag(const char *w1, const char *w2, const char *w3, const char *w4, const char *start, const char *buffer, const char *filepath, int *retval) { int16 m; char mapname[32]; @@ -4055,8 +4802,7 @@ const char *npc_parse_mapflag(const char *w1, const char *w2, const char *w3, co if (!strcmpi(w3, "nosave")) { char savemap[32]; int savex, savey; - if (state == 0) - ; //Map flag disabled. + if (state == 0); //Map flag disabled. else if (w4 && !strcmpi(w4, "SavePoint")) { map->list[m].save.map = 0; map->list[m].save.x = -1; @@ -4108,6 +4854,11 @@ const char *npc_parse_mapflag(const char *w1, const char *w2, const char *w3, co ShowWarning("npc_parse_mapflag: You can't set PvP and GvG flags for the same map! Removing GvG flags from %s in file '%s', line '%d'.\n", map->list[m].name, filepath, strline(buffer,start-buffer)); if (retval) *retval = EXIT_FAILURE; } + if (state && map->list[m].flag.cvc) { + map->list[m].flag.cvc = 0; + ShowWarning("npc_parse_mapflag: You can't set CvC and PvP flags for the same map! Removing CvC flag from %s in file '%s', line '%d'.\n", map->list[m].name, filepath, strline(buffer, start-buffer)); + if (retval) *retval = EXIT_FAILURE; + } if( state && map->list[m].flag.battleground ) { map->list[m].flag.battleground = 0; ShowWarning("npc_parse_mapflag: You can't set PvP and BattleGround flags for the same map! Removing BattleGround flag from %s in file '%s', line '%d'.\n", map->list[m].name, filepath, strline(buffer,start-buffer)); @@ -4160,6 +4911,11 @@ const char *npc_parse_mapflag(const char *w1, const char *w2, const char *w3, co ShowWarning("npc_parse_mapflag: You can't set PvP and GvG flags for the same map! Removing PvP flag from %s in file '%s', line '%d'.\n", map->list[m].name, filepath, strline(buffer,start-buffer)); if (retval) *retval = EXIT_FAILURE; } + if (state && map->list[m].flag.cvc) { + map->list[m].flag.cvc = 0; + ShowWarning("npc_parse_mapflag: You can't set CvC and GvG flags for the same map! Removing CvC flag from %s in file '%s', line '%d'.\n", map->list[m].name, filepath, strline(buffer, start-buffer)); + if (retval) *retval = EXIT_FAILURE; + } if( state && map->list[m].flag.battleground ) { map->list[m].flag.battleground = 0; ShowWarning("npc_parse_mapflag: You can't set GvG and BattleGround flags for the same map! Removing BattleGround flag from %s in file '%s', line '%d'.\n", map->list[m].name, filepath, strline(buffer,start-buffer)); @@ -4202,11 +4958,47 @@ const char *npc_parse_mapflag(const char *w1, const char *w2, const char *w3, co ShowWarning("npc_parse_mapflag: You can't set GvG and BattleGround flags for the same map! Removing GvG flag from %s in file '%s', line '%d'.\n", map->list[m].name, filepath, strline(buffer,start-buffer)); if (retval) *retval = EXIT_FAILURE; } + if (map->list[m].flag.cvc) { + map->list[m].flag.cvc = 0; + ShowWarning("npc_parse_mapflag: You can't set CvC and BattleGround flags for the same map! Removing CvC flag from %s in file '%s', line '%d'.\n", map->list[m].name, filepath, strline(buffer, start-buffer)); + if (retval) *retval = EXIT_FAILURE; + } if( state && (zone = strdb_get(map->zone_db, MAP_ZONE_BG_NAME)) != NULL && map->list[m].zone != zone ) { map->zone_change(m,zone,start,buffer,filepath); } } + else if (!strcmpi(w3, "cvc")) { + struct map_zone_data *zone; + + map->list[m].flag.cvc = state; + if (state && (map->list[m].flag.gvg || map->list[m].flag.gvg_dungeon || map->list[m].flag.gvg_castle)) { + map->list[m].flag.gvg = 0; + map->list[m].flag.gvg_dungeon = 0; + map->list[m].flag.gvg_castle = 0; + ShowWarning("npc_parse_mapflag: You can't set GvG and CvC flags for the same map! Removing GvG flag from %s in file '%s', line '%d'.\n", map->list[m].name, filepath, strline(buffer, start-buffer)); + if (retval) { + *retval = EXIT_FAILURE; + } + } + if (state && map->list[m].flag.pvp) { + map->list[m].flag.pvp = 0; + ShowWarning("npc_parse_mapflag: You can't set PvP and CvC flags for the same map! Removing PvP flag from %s in file '%s', line '%d'.\n", map->list[m].name, filepath, strline(buffer, start-buffer)); + if (retval) { + *retval = EXIT_FAILURE; + } + } + if (state && map->list[m].flag.battleground) { + map->list[m].flag.battleground = 0; + ShowWarning("npc_parse_mapflag: You can't set CvC and BattleGround flags for the same map! Removing BattleGround flag from %s in file '%s', line '%d'.\n", map->list[m].name, filepath, strline(buffer, start-buffer)); + if (retval) { + *retval = EXIT_FAILURE; + } + } + if (state && (zone = strdb_get(map->zone_db, MAP_ZONE_CVC_NAME)) != NULL && map->list[m].zone != zone) { + map->zone_change(m, zone, start, buffer, filepath); + } + } else if (!strcmpi(w3,"noexppenalty")) map->list[m].flag.noexppenalty=state; else if (!strcmpi(w3,"nozenypenalty")) @@ -4284,6 +5076,8 @@ const char *npc_parse_mapflag(const char *w1, const char *w2, const char *w3, co map->list[m].flag.reset=state; else if (!strcmpi(w3,"notomb")) map->list[m].flag.notomb=state; + else if (!strcmpi(w3, "noautoloot")) + map->list[m].flag.noautoloot = state; else if (!strcmpi(w3,"adjust_unit_duration")) { int skill_id, k; char skill_name[MAP_ZONE_MAPFLAG_LENGTH], modifier[MAP_ZONE_MAPFLAG_LENGTH]; @@ -4301,7 +5095,8 @@ const char *npc_parse_mapflag(const char *w1, const char *w2, const char *w3, co } } - if( modifier[0] == '\0' ) { + if (state == 0); //Map flag disabled. + else if (modifier[0] == '\0') { ShowWarning("npc_parse_mapflag: Missing 5th param for 'adjust_unit_duration' flag! removing flag from %s in file '%s', line '%d'.\n", map->list[m].name, filepath, strline(buffer,start-buffer)); if (retval) *retval = EXIT_FAILURE; } else if( !( skill_id = skill->name2id(skill_name) ) || !skill->get_unit_id( skill->name2id(skill_name), 0) ) { @@ -4360,7 +5155,8 @@ const char *npc_parse_mapflag(const char *w1, const char *w2, const char *w3, co } } - if( modifier[0] == '\0' ) { + if (state == 0); //Map flag disabled. + else if (modifier[0] == '\0') { ShowWarning("npc_parse_mapflag: Missing 5th param for 'adjust_skill_damage' flag! removing flag from %s in file '%s', line '%d'.\n", map->list[m].name, filepath, strline(buffer,start-buffer)); if (retval) *retval = EXIT_FAILURE; } else if( !( skill_id = skill->name2id(skill_name) ) ) { @@ -4432,6 +5228,14 @@ const char *npc_parse_mapflag(const char *w1, const char *w2, const char *w3, co map->list[m].flag.nocashshop = (state) ? 1 : 0; } else if (!strcmpi(w3,"noviewid")) { map->list[m].flag.noviewid = (state) ? atoi(w4) : 0; + } else if (!strcmpi(w3, "pairship_startable")) { + map->list[m].flag.pairship_startable = (state) ? 1 : 0; + } else if (!strcmpi(w3, "pairship_endable")) { + map->list[m].flag.pairship_endable = (state) ? 1 : 0; + } else if (!strcmpi(w3, "nostorage")) { + map->list[m].flag.nostorage = (state) ? cap_value(atoi(w4), 1, 3) : 0; + } else if (!strcmpi(w3, "nogstorage")) { + map->list[m].flag.nogstorage = (state) ? cap_value(atoi(w4), 1, 3) : 0; } else { npc->parse_unknown_mapflag(mapname, w3, w4, start, buffer, filepath, retval); } @@ -4456,7 +5260,7 @@ const char *npc_parse_mapflag(const char *w1, const char *w2, const char *w3, co * (EXIT_FAILURE) status. May be NULL. * @return A pointer to the advanced buffer position. */ -const char *npc_parse_unknown_object(const char *w1, const char *w2, const char *w3, const char *w4, const char *start, const char *buffer, const char *filepath, int *retval) +static const char *npc_parse_unknown_object(const char *w1, const char *w2, const char *w3, const char *w4, const char *start, const char *buffer, const char *filepath, int *retval) { nullpo_retr(start, retval); ShowError("npc_parsesrcfile: Unable to parse, probably a missing or extra TAB in file '%s', line '%d'. Skipping line...\n * w1=%s\n * w2=%s\n * w3=%s\n * w4=%s\n", filepath, strline(buffer,start-buffer), w1, w2, w3, w4); @@ -4474,7 +5278,8 @@ const char *npc_parse_unknown_object(const char *w1, const char *w2, const char * @retval EXIT_SUCCESS if filepath was loaded correctly. * @retval EXIT_FAILURE if there were errors/warnings when loading filepath. */ -int npc_parsesrcfile(const char* filepath, bool runOnInit) { +static int npc_parsesrcfile(const char *filepath, bool runOnInit) +{ int success = EXIT_SUCCESS; int16 m, x, y; int lines = 0; @@ -4644,7 +5449,7 @@ int npc_parsesrcfile(const char* filepath, bool runOnInit) { { p = npc->parse_duplicate(w1,w2,w3,w4, p, buffer, filepath, (runOnInit?NPO_ONINIT:NPO_NONE), &success); } - else if( (strcmp(w2,"monster") == 0 || strcmp(w2,"boss_monster") == 0) ) + else if (strcmp(w2,"monster") == 0 || strcmp(w2,"boss_monster") == 0 || strcmp(w2,"miniboss_monster") == 0) { p = npc->parse_mob(w1, w2, w3, w4, p, buffer, filepath, &success); } @@ -4662,7 +5467,7 @@ int npc_parsesrcfile(const char* filepath, bool runOnInit) { return success; } -int npc_script_event(struct map_session_data* sd, enum npce_event type) +static int npc_script_event(struct map_session_data *sd, enum npce_event type) { int i; if (type == NPCE_MAX) @@ -4676,7 +5481,7 @@ int npc_script_event(struct map_session_data* sd, enum npce_event type) return i; } -void npc_read_event_script(void) +static void npc_read_event_script(void) { int i; struct { @@ -4739,7 +5544,7 @@ void npc_read_event_script(void) /** * @see DBApply */ -int npc_path_db_clear_sub(union DBKey key, struct DBData *data, va_list args) +static int npc_path_db_clear_sub(union DBKey key, struct DBData *data, va_list args) { struct npc_path_data *npd = DB->data2ptr(data); nullpo_ret(npd); @@ -4751,7 +5556,7 @@ int npc_path_db_clear_sub(union DBKey key, struct DBData *data, va_list args) /** * @see DBApply */ -int npc_ev_label_db_clear_sub(union DBKey key, struct DBData *data, va_list args) +static int npc_ev_label_db_clear_sub(union DBKey key, struct DBData *data, va_list args) { struct linkdb_node **label_linkdb = DB->data2ptr(data); linkdb_final(label_linkdb); // linked data (struct event_data*) is freed when clearing ev_db @@ -4762,7 +5567,8 @@ int npc_ev_label_db_clear_sub(union DBKey key, struct DBData *data, va_list args * Main npc file processing * @param npc_min Minimum npc id - used to know how many NPCs were loaded **/ -void npc_process_files( int npc_min ) { +static void npc_process_files(int npc_min) +{ struct npc_src_list *file; // Current file ShowStatus("Loading NPCs...\r"); @@ -4778,133 +5584,153 @@ void npc_process_files( int npc_min ) { "\t-'"CL_WHITE"%d"CL_RESET"' Spawn sets\n" "\t-'"CL_WHITE"%d"CL_RESET"' Mobs Cached\n" "\t-'"CL_WHITE"%d"CL_RESET"' Mobs Not Cached\n", - npc_id - npc_min, npc_warp, npc_shop, npc_script, npc_mob, npc_cache_mob, npc_delay_mob); + npc->npc_id - npc_min, npc->npc_warp, npc->npc_shop, npc->npc_script, npc->npc_mob, npc->npc_cache_mob, npc->npc_delay_mob); } -//Clear then reload npcs files -int npc_reload(void) { - int npc_new_min = npc_id; - struct s_mapiterator* iter; - struct block_list* bl; - - if (map->retval == EXIT_FAILURE) - map->retval = EXIT_SUCCESS; // Clear return status in case something failed before. - - /* clear guild flag cache */ - guild->flags_clear(); +/** + * Clears and then reloads all NPC files. + * + * @return Always 0. + * + **/ +static int npc_reload(void) +{ + if (map->retval == EXIT_FAILURE) /// Clear return status in case something failed before. + map->retval = EXIT_SUCCESS; + guild->flags_clear(); /// Clear guild flag cache. npc->path_db->clear(npc->path_db, npc->path_db_clear_sub); - db_clear(npc->name_db); db_clear(npc->ev_db); npc->ev_label_db->clear(npc->ev_label_db, npc->ev_label_db_clear_sub); + npc->npc_last_npd = NULL; + npc->npc_last_path = NULL; + npc->npc_last_ref = NULL; + + const int npc_new_min = npc->npc_id; + struct s_mapiterator *iter = mapit_geteachiddb(); + + /** Remove all NPCs/mobs. [Skotlex] **/ + for (struct block_list *bl = mapit->first(iter); mapit->exists(iter); bl = mapit->next(iter)) { + switch (bl->type) { + case BL_NPC: + if (bl->id != npc->fake_nd->bl.id) /// Don't remove fake_nd. + npc->unload(BL_UCAST(BL_NPC, bl), false, false); - npc_last_npd = NULL; - npc_last_path = NULL; - npc_last_ref = NULL; - - //Remove all npcs/mobs. [Skotlex] - iter = mapit_geteachiddb(); - for (bl = mapit->first(iter); mapit->exists(iter); bl = mapit->next(iter)) { - switch(bl->type) { - case BL_NPC: - if( bl->id != npc->fake_nd->bl.id )// don't remove fake_nd - npc->unload(BL_UCAST(BL_NPC, bl), false); - break; - case BL_MOB: - unit->free(bl,CLR_OUTSIGHT); - break; + break; + case BL_MOB: + unit->free(bl, CLR_OUTSIGHT); + break; + default: + break; } } + mapit->free(iter); - if(battle_config.dynamic_mobs) {// dynamic check by [random] - int16 m; - for (m = 0; m < map->count; m++) { - int16 i; - for (i = 0; i < MAX_MOB_LIST_PER_MAP; i++) { + if (battle_config.dynamic_mobs) { /// Dynamic check. [random] + for (int m = 0; m < map->count; m++) { + for (int i = 0; i < MAX_MOB_LIST_PER_MAP; i++) { if (map->list[m].moblist[i] != NULL) { aFree(map->list[m].moblist[i]); map->list[m].moblist[i] = NULL; } - if( map->list[m].mob_delete_timer != INVALID_TIMER ) - { // Mobs were removed anyway,so delete the timer [Inkfish] + + if (map->list[m].mob_delete_timer != INVALID_TIMER) { /// Mobs were removed anyway, so delete the timer. [Inkfish] timer->delete(map->list[m].mob_delete_timer, map->removemobs_timer); map->list[m].mob_delete_timer = INVALID_TIMER; } } + if (map->list[m].npc_num > 0) - ShowWarning("npc_reload: %d npcs weren't removed at map %s!\n", map->list[m].npc_num, map->list[m].name); + ShowWarning("npc_reload: %d NPCs weren't removed from map %s!\n", + map->list[m].npc_num, map->list[m].name); } } - // clear mob spawn lookup index mob->clear_spawninfo(); - - npc_warp = npc_shop = npc_script = 0; - npc_mob = npc_cache_mob = npc_delay_mob = 0; - - // reset mapflags + npc->npc_warp = 0; + npc->npc_shop = 0; + npc->npc_script = 0; + npc->npc_mob = 0; + npc->npc_cache_mob = 0; + npc->npc_delay_mob = 0; + map->zone_reload(); map->flags_init(); - - // Reprocess npc files and reload constants itemdb->name_constants(); - npc_process_files( npc_new_min ); - + clan->set_constants(); + npc_process_files(npc_new_min); instance->reload(); - map->zone_init(); - - npc->motd = npc->name2id("HerculesMOTD"); /* [Ind/Hercules] */ - - //Re-read the NPC Script Events cache. + npc->motd = npc->name2id("HerculesMOTD"); /// [Ind/Hercules] npc->read_event_script(); - // Execute main initialisation events - // The correct initialisation order is: - // OnInit -> OnInterIfInit -> OnInterIfInitOnce -> OnAgitInit -> OnAgitInit2 - npc->event_do_oninit( true ); + /** + * Execute main initialization events + * The correct initialization order is: + * OnInit -> OnInterIfInit -> OnInterIfInitOnce -> OnAgitInit -> OnAgitInit2 + * + **/ + npc->event_do_oninit(true); + npc->market_fromsql(); - // Execute rest of the startup events if connected to char-server. [Lance] - // Executed when connection is established with char-server in chrif_connectack - if( !intif->CheckForCharServer() ) { - ShowStatus("Event '"CL_WHITE"OnInterIfInit"CL_RESET"' executed with '"CL_WHITE"%d"CL_RESET"' NPCs.\n", npc->event_doall("OnInterIfInit")); - ShowStatus("Event '"CL_WHITE"OnInterIfInitOnce"CL_RESET"' executed with '"CL_WHITE"%d"CL_RESET"' NPCs.\n", npc->event_doall("OnInterIfInitOnce")); - } - // Refresh guild castle flags on both woe setups - // These events are only executed after receiving castle information from char-server + npc->barter_fromsql(); + npc->expanded_barter_fromsql(); + + /* + * Execute rest of the startup events if connected to char-server. [Lance] + * Executed when connection is established with char-server in chrif_connectack(). + */ + if (intif->CheckForCharServer() == 0) { + ShowStatus("Event '"CL_WHITE"OnInterIfInit"CL_RESET"' executed with '"CL_WHITE"%d"CL_RESET"' NPCs.\n", + npc->event_doall("OnInterIfInit")); + ShowStatus("Event '"CL_WHITE"OnInterIfInitOnce"CL_RESET"' executed with '"CL_WHITE"%d"CL_RESET"' NPCs.\n", + npc->event_doall("OnInterIfInitOnce")); + } + + /* + * Refresh guild castle flags on both WoE setups. + * These events are only executed after receiving castle information from char-server. + */ npc->event_doall("OnAgitInit"); npc->event_doall("OnAgitInit2"); return 0; } -//Unload all npc in the given file -bool npc_unloadfile(const char *filepath) +/** + * Unloads all NPCs in the given file. + * + * @param filepath Path to the file which should be unloaded. + * @param unload_mobs If true, mobs spawned by NPCs in the file will be removed. + * @return true if at least one NPC was unloaded, otherwise false. + * + **/ +static bool npc_unloadfile(const char *filepath, bool unload_mobs) { + nullpo_retr(false, filepath); + struct DBIterator *iter = db_iterator(npc->name_db); - struct npc_data* nd = NULL; bool found = false; - nullpo_retr(false, filepath); - - for( nd = dbi_first(iter); dbi_exists(iter); nd = dbi_next(iter) ) { - if( nd->path && strcasecmp(nd->path,filepath) == 0 ) { // FIXME: This can break in case-sensitive file systems + for (struct npc_data *nd = dbi_first(iter); dbi_exists(iter); nd = dbi_next(iter)) { + if (nd->path != NULL && strcasecmp(nd->path, filepath) == 0) { // FIXME: This can break in case-sensitive file systems. found = true; - npc->unload_duplicates(nd);/* unload any npcs which could duplicate this but be in a different file */ - npc->unload(nd, true); + npc->unload_duplicates(nd, unload_mobs); /// Unload any NPC which could duplicate this but be in a different file. + npc->unload(nd, true, unload_mobs); } } dbi_destroy(iter); - if( found ) /* refresh event cache */ + if (found) /// Refresh event cache. npc->read_event_script(); return found; } -void do_clear_npc(void) { +static void do_clear_npc(void) +{ db_clear(npc->name_db); db_clear(npc->ev_db); npc->ev_label_db->clear(npc->ev_label_db, npc->ev_label_db_clear_sub); @@ -4913,7 +5739,8 @@ void do_clear_npc(void) { /*========================================== * Destructor *------------------------------------------*/ -int do_final_npc(void) { +static int do_final_npc(void) +{ db_destroy(npc->ev_db); npc->ev_label_db->destroy(npc->ev_label_db, npc->ev_label_db_clear_sub); db_destroy(npc->name_db); @@ -4924,7 +5751,8 @@ int do_final_npc(void) { return 0; } -void npc_debug_warps_sub(struct npc_data* nd) { +static void npc_debug_warps_sub(struct npc_data *nd) +{ int16 m; nullpo_retv(nd); @@ -4952,49 +5780,57 @@ void npc_debug_warps_sub(struct npc_data* nd) { } } -static void npc_debug_warps(void) { +static void npc_debug_warps(void) +{ int16 m, i; for (m = 0; m < map->count; m++) for (i = 0; i < map->list[m].npc_num; i++) npc->debug_warps_sub(map->list[m].npc[i]); } +static void npc_questinfo_clear(struct npc_data *nd) +{ + nullpo_retv(nd); + + for (int i = 0; i < VECTOR_LENGTH(nd->qi_data); i++) { + struct questinfo *qi = &VECTOR_INDEX(nd->qi_data, i); + VECTOR_CLEAR(qi->items); + VECTOR_CLEAR(qi->quest_requirement); + } + VECTOR_CLEAR(nd->qi_data); +} + /*========================================== * npc initialization *------------------------------------------*/ -int do_init_npc(bool minimal) { +static int do_init_npc(bool minimal) +{ int i; - memset(&npc->base_ud, 0, sizeof( struct unit_data) ); - npc->base_ud.bl = NULL; - npc->base_ud.walktimer = INVALID_TIMER; - npc->base_ud.skilltimer = INVALID_TIMER; - npc->base_ud.attacktimer = INVALID_TIMER; - npc->base_ud.attackabletime = - npc->base_ud.canact_tick = - npc->base_ud.canmove_tick = timer->gettick(); + unit->init_ud(&npc->base_ud); + npc->base_ud.bl = NULL; //Stock view data for normal npcs. memset(&npc_viewdb, 0, sizeof(npc_viewdb)); - npc_viewdb[0].class_ = INVISIBLE_CLASS; //Invisible class is stored here. + npc_viewdb[0].class = INVISIBLE_CLASS; //Invisible class is stored here. for( i = 1; i < MAX_NPC_CLASS; i++ ) - npc_viewdb[i].class_ = i; + npc_viewdb[i].class = i; for( i = MAX_NPC_CLASS2_START; i < MAX_NPC_CLASS2_END; i++ ) - npc_viewdb2[i - MAX_NPC_CLASS2_START].class_ = i; - + npc_viewdb2[i - MAX_NPC_CLASS2_START].class = i; npc->ev_db = strdb_alloc(DB_OPT_DUP_KEY|DB_OPT_RELEASE_DATA, EVENT_NAME_LENGTH); npc->ev_label_db = strdb_alloc(DB_OPT_DUP_KEY|DB_OPT_RELEASE_DATA, NAME_LENGTH); npc->name_db = strdb_alloc(DB_OPT_BASE, NAME_LENGTH); npc->path_db = strdb_alloc(DB_OPT_DUP_KEY|DB_OPT_RELEASE_DATA, 0); - npc_last_npd = NULL; - npc_last_path = NULL; - npc_last_ref = NULL; + npc->npc_last_npd = NULL; + npc->npc_last_path = NULL; + npc->npc_last_ref = NULL; // Should be loaded before npc processing, otherwise labels could overwrite constant values // and lead to undefined behavior [Panikon] itemdb->name_constants(); + clan->set_constants(); if (!minimal) { npc->timer_event_ers = ers_new(sizeof(struct timer_event_data),"clif.c::timer_event_ers",ERS_OPT_NONE); @@ -5019,12 +5855,6 @@ int do_init_npc(bool minimal) { timer->add_func_list(npc->timerevent,"npc_timerevent"); } - if( script->lang_export_fp ) { - ShowInfo("Lang exported to '%s'\n",script->lang_export_file); - fclose(script->lang_export_fp); - script->lang_export_fp = NULL; - } - // Init dummy NPC CREATE(npc->fake_nd, struct npc_data, 1); npc->fake_nd->bl.m = -1; @@ -5034,7 +5864,7 @@ int do_init_npc(bool minimal) { strcpy(npc->fake_nd->name,"FAKE_NPC"); memcpy(npc->fake_nd->exname, npc->fake_nd->name, 9); - npc_script++; + npc->npc_script++; npc->fake_nd->bl.type = BL_NPC; npc->fake_nd->subtype = SCRIPT; @@ -5045,9 +5875,21 @@ int do_init_npc(bool minimal) { return 0; } -void npc_defaults(void) { +void npc_defaults(void) +{ npc = &npc_s; + npc->npc_id = START_NPC_NUM; + npc->npc_warp = 0; + npc->npc_shop = 0; + npc->npc_script = 0; + npc->npc_mob = 0; + npc->npc_delay_mob = 0; + npc->npc_cache_mob = 0; + npc->npc_last_path = NULL; + npc->npc_last_ref = NULL; + npc->npc_last_npd = NULL; + npc->motd = NULL; npc->ev_db = NULL; npc->ev_label_db = NULL; @@ -5114,6 +5956,7 @@ void npc_defaults(void) { npc->unload_ev_label = npc_unload_ev_label; npc->unload_dup_sub = npc_unload_dup_sub; npc->unload_duplicates = npc_unload_duplicates; + npc->unload_mob = npc_unload_mob; npc->unload = npc_unload; npc->clearsrcfile = npc_clearsrcfile; npc->addsrcfile = npc_addsrcfile; @@ -5166,10 +6009,22 @@ void npc_defaults(void) { npc->trader_pay = npc_trader_pay; npc->trader_update = npc_trader_update; npc->market_buylist = npc_market_buylist; + npc->barter_buylist = npc_barter_buylist; + npc->expanded_barter_buylist = npc_expanded_barter_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; npc->market_delfromsql_sub = npc_market_delfromsql_sub; + npc->barter_fromsql = npc_barter_fromsql; + npc->barter_tosql = npc_barter_tosql; + npc->barter_delfromsql = npc_barter_delfromsql; + npc->barter_delfromsql_sub = npc_barter_delfromsql_sub; + npc->expanded_barter_fromsql = npc_expanded_barter_fromsql; + npc->expanded_barter_tosql = npc_expanded_barter_tosql; + npc->expanded_barter_delfromsql = npc_expanded_barter_delfromsql; + npc->expanded_barter_delfromsql_sub = npc_expanded_barter_delfromsql_sub; npc->db_checkid = npc_db_checkid; + npc->refresh = npc_refresh; + npc->questinfo_clear = npc_questinfo_clear; } |