/src/map/npc.c
C | 5037 lines | 3643 code | 686 blank | 708 comment | 1384 complexity | 046ed99c92970bd35eb9e79d1dd1adc9 MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.0
Large files files are truncated, but you can click here to view the full file
- /**
- * 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
- *
- * Hercules is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- #define HERCULES_CORE
- #include "config/core.h" // NPC_SECURE_TIMEOUT_INPUT, NPC_SECURE_TIMEOUT_MENU, NPC_SECURE_TIMEOUT_NEXT, SECURE_NPCTIMEOUT, SECURE_NPCTIMEOUT_INTERVAL
- #include "npc.h"
- #include "map/battle.h"
- #include "map/chat.h"
- #include "map/clif.h"
- #include "map/guild.h"
- #include "map/instance.h"
- #include "map/intif.h"
- #include "map/itemdb.h"
- #include "map/log.h"
- #include "map/map.h"
- #include "map/mob.h"
- #include "map/pc.h"
- #include "map/pet.h"
- #include "map/script.h"
- #include "map/skill.h"
- #include "map/status.h"
- #include "map/unit.h"
- #include "common/HPM.h"
- #include "common/cbasetypes.h"
- #include "common/db.h"
- #include "common/ers.h"
- #include "common/memmgr.h"
- #include "common/nullpo.h"
- #include "common/showmsg.h"
- #include "common/socket.h"
- #include "common/strlib.h"
- #include "common/timer.h"
- #include "common/utils.h"
- #include <errno.h>
- #include <math.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <time.h>
- 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 struct script_event_s {
- //Holds pointers to the commonly executed scripts for speedup. [Skotlex]
- struct event_data *event[UCHAR_MAX];
- const char *event_name[UCHAR_MAX];
- uint8 event_count;
- } script_event[NPCE_MAX];
- /**
- * Returns the viewdata for normal npc classes.
- * @param class_ The NPC class ID.
- * @return The viewdata, or NULL if the ID is invalid.
- */
- struct view_data *npc_get_viewdata(int class_)
- {
- if (class_ == INVISIBLE_CLASS)
- return &npc_viewdb[0];
- if (npc->db_checkid(class_)) {
- if (class_ < MAX_NPC_CLASS) {
- return &npc_viewdb[class_];
- } else if (class_ >= MAX_NPC_CLASS2_START && class_ < MAX_NPC_CLASS2_END) {
- return &npc_viewdb2[class_-MAX_NPC_CLASS2_START];
- }
- }
- return NULL;
- }
- /**
- * Checks if a given id is a valid npc id.
- *
- * Since new npcs are added all the time, the max valid value is the one before the first mob (Scorpion = 1001)
- *
- * @param id The NPC ID to validate.
- * @return Whether the value is a valid ID.
- */
- bool npc_db_checkid(int id)
- {
- if (id >= WARP_CLASS && id <= 125) // First subrange
- return true;
- if (id == HIDDEN_WARP_CLASS || id == INVISIBLE_CLASS) // Special IDs not included in the valid ranges
- return true;
- if (id > 400 && id < MAX_NPC_CLASS) // Second subrange
- return true;
- if (id >= MAX_NPC_CLASS2_START && id < MAX_NPC_CLASS2_END) // Second range
- 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
- 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
- }
- // full loop, nothing available
- ShowFatalError("npc_get_new_npc_id: All ids are taken. Exiting...");
- exit(1);
- }
- }
- int npc_isnear_sub(struct block_list *bl, va_list args)
- {
- const struct npc_data *nd = NULL;
- nullpo_ret(bl);
- Assert_ret(bl->type == BL_NPC);
- nd = BL_UCCAST(BL_NPC, bl);
- if( nd->option & (OPTION_HIDE|OPTION_INVISIBLE) )
- return 0;
- if( battle_config.vendchat_near_hiddennpc && ( nd->class_ == FAKE_NPC || nd->class_ == HIDDEN_WARP_CLASS ) )
- return 0;
- return 1;
- }
- 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;
- return false;
- }
- int npc_ontouch_event(struct map_session_data *sd, struct npc_data *nd)
- {
- char name[EVENT_NAME_LENGTH];
- if( nd->touching_id )
- return 0; // Attached a player already. Can't trigger on anyone else.
- if( pc_ishiding(sd) )
- return 1; // Can't trigger 'OnTouch_'. try 'OnTouch' later.
- snprintf(name, ARRAYLENGTH(name), "%s::%s", nd->exname, script->config.ontouch_name);
- return npc->event(sd,name,1);
- }
- int npc_ontouch2_event(struct map_session_data *sd, struct npc_data *nd)
- {
- char name[EVENT_NAME_LENGTH];
- if (sd->areanpc_id == nd->bl.id)
- return 0;
- snprintf(name, ARRAYLENGTH(name), "%s::%s", nd->exname, script->config.ontouch2_name);
- return npc->event(sd, name, 2);
- }
- int npc_onuntouch_event(struct map_session_data *sd, struct npc_data *nd)
- {
- char name[EVENT_NAME_LENGTH];
- if (sd->areanpc_id != nd->bl.id)
- return 0;
- snprintf(name, ARRAYLENGTH(name), "%s::%s", nd->exname, script->config.onuntouch_name);
- return npc->event(sd, name, 2);
- }
- /*==========================================
- * Sub-function of npc_enable, runs OnTouch event when enabled
- *------------------------------------------*/
- int npc_enable_sub(struct block_list *bl, va_list ap)
- {
- struct npc_data *nd;
- nullpo_ret(bl);
- nullpo_ret(nd=va_arg(ap,struct npc_data *));
- if (bl->type == BL_PC) {
- struct map_session_data *sd = BL_UCAST(BL_PC, bl);
- if (nd->option&OPTION_INVISIBLE)
- return 1;
- if( npc->ontouch_event(sd,nd) > 0 && npc->ontouch2_event(sd,nd) > 0 )
- { // failed to run OnTouch event, so just click the npc
- if (sd->npc_id != 0)
- return 0;
- pc_stop_walking(sd, STOPWALKING_FLAG_FIXPOS);
- npc->click(sd,nd);
- }
- }
- return 0;
- }
- /*==========================================
- * Disable / Enable NPC
- *------------------------------------------*/
- int npc_enable(const char* name, int flag)
- {
- struct npc_data* nd = npc->name2id(name);
- if ( nd == NULL ) {
- ShowError("npc_enable: Attempted to %s a non-existing NPC '%s' (flag=%d).\n", (flag&3) ? "show" : "hide", name, flag);
- return 0;
- }
- if (flag&1) {
- nd->option&=~OPTION_INVISIBLE;
- clif->spawn(&nd->bl);
- } else if (flag&2)
- nd->option&=~OPTION_HIDE;
- else if (flag&4)
- nd->option|= OPTION_HIDE;
- else { //Can't change the view_data to invisible class because the view_data for all npcs is shared! [Skotlex]
- nd->option|= OPTION_INVISIBLE;
- clif->clearunit_area(&nd->bl,CLR_OUTSIGHT); // Hack to trick maya purple card [Xazax]
- }
- if (nd->class_ == WARP_CLASS || nd->class_ == FLAG_CLASS) { //Client won't display option changes for these classes [Toms]
- if (nd->option&(OPTION_HIDE|OPTION_INVISIBLE))
- clif->clearunit_area(&nd->bl, CLR_OUTSIGHT);
- else
- clif->spawn(&nd->bl);
- } else
- clif->changeoption(&nd->bl);
- if( flag&3 && (nd->u.scr.xs >= 0 || nd->u.scr.ys >= 0) ) //check if player standing on a OnTouchArea
- map->foreachinarea( npc->enable_sub, nd->bl.m, nd->bl.x-nd->u.scr.xs, nd->bl.y-nd->u.scr.ys, nd->bl.x+nd->u.scr.xs, nd->bl.y+nd->u.scr.ys, BL_PC, nd );
- return 0;
- }
- /*==========================================
- * NPC lookup (get npc_data through npcname)
- *------------------------------------------*/
- struct npc_data *npc_name2id(const char *name)
- {
- return strdb_get(npc->name_db, name);
- }
- /**
- * For the Secure NPC Timeout option (check config/Secure.h) [RR]
- **/
- /**
- * 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) {
- #ifdef SECURE_NPCTIMEOUT
- struct map_session_data* sd = NULL;
- unsigned int timeout = NPC_SECURE_TIMEOUT_NEXT;
- if( (sd = map->id2sd(id)) == NULL || !sd->npc_id ) {
- if( sd ) sd->npc_idle_timer = INVALID_TIMER;
- return 0;//Not logged in anymore OR no longer attached to a npc
- }
- switch( sd->npc_idle_type ) {
- case NPCT_INPUT:
- timeout = NPC_SECURE_TIMEOUT_INPUT;
- break;
- case NPCT_MENU:
- timeout = NPC_SECURE_TIMEOUT_MENU;
- break;
- //case NPCT_WAIT: var starts with this value
- }
- if( DIFF_TICK(tick,sd->npc_idle_tick) > (timeout*1000) ) {
- /**
- * If we still have the NPC script attached, tell it to stop.
- **/
- if( sd->st )
- sd->st->state = END;
- sd->state.menu_or_input = 0;
- sd->npc_menu = 0;
- /**
- * This guy's been idle for longer than allowed, close him.
- **/
- clif->scriptclose(sd,sd->npc_id);
- sd->npc_idle_timer = INVALID_TIMER;
- /**
- * We will end the script ourselves, client will request to end it again if it have dialog,
- * however it will be ignored, workaround for client stuck if NPC have no dialog. [hemagx]
- **/
- sd->state.dialog = 0;
- npc->scriptcont(sd, sd->npc_id, true);
- } else //Create a new instance of ourselves to continue
- sd->npc_idle_timer = timer->add(timer->gettick() + (SECURE_NPCTIMEOUT_INTERVAL*1000),npc->secure_timeout_timer,sd->bl.id,0);
- #endif
- return 0;
- }
- /*==========================================
- * Dequeue event and add timer for execution (100ms)
- *------------------------------------------*/
- int npc_event_dequeue(struct map_session_data* sd)
- {
- nullpo_ret(sd);
- if(sd->npc_id) { //Current script is aborted.
- if(sd->state.using_fake_npc){
- clif->clearunit_single(sd->npc_id, CLR_OUTSIGHT, sd->fd);
- sd->state.using_fake_npc = 0;
- }
- if (sd->st) {
- script->free_state(sd->st);
- sd->st = NULL;
- }
- sd->npc_id = 0;
- }
- if (!sd->eventqueue[0][0])
- return 0; //Nothing to dequeue
- if (!pc->addeventtimer(sd,100,sd->eventqueue[0])) { //Failed to dequeue, couldn't set a timer.
- ShowWarning("npc_event_dequeue: event timer is full !\n");
- return 0;
- }
- //Event dequeued successfully, shift other elements.
- memmove(sd->eventqueue[0], sd->eventqueue[1], (MAX_EVENTQUEUE-1)*sizeof(sd->eventqueue[0]));
- sd->eventqueue[MAX_EVENTQUEUE-1][0]=0;
- return 1;
- }
- /**
- * @see DBCreateData
- */
- DBData npc_event_export_create(DBKey key, va_list args)
- {
- struct linkdb_node** head_ptr;
- CREATE(head_ptr, struct linkdb_node*, 1);
- *head_ptr = NULL;
- return DB->ptr2data(head_ptr);
- }
- /*==========================================
- * exports a npc event label
- * called from npc_parse_script
- *------------------------------------------*/
- int npc_event_export(struct npc_data *nd, int i)
- {
- char* lname = nd->u.scr.label_list[i].name;
- int pos = nd->u.scr.label_list[i].pos;
- if ((lname[0] == 'O' || lname[0] == 'o') && (lname[1] == 'N' || lname[1] == 'n')) {
- struct event_data *ev;
- struct linkdb_node **label_linkdb = NULL;
- char buf[EVENT_NAME_LENGTH];
- snprintf(buf, ARRAYLENGTH(buf), "%s::%s", nd->exname, lname);
- if (strdb_exists(npc->ev_db, buf)) // There was already another event of the same name?
- return 1;
- // generate the data and insert it
- CREATE(ev, struct event_data, 1);
- ev->nd = nd;
- ev->pos = pos;
- strdb_put(npc->ev_db, buf, ev);
- label_linkdb = strdb_ensure(npc->ev_label_db, lname, npc->event_export_create);
- linkdb_insert(label_linkdb, nd, ev);
- }
- return 0;
- }
- 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)
- {
- struct event_data* ev = data;
- int* c;
- const char* name;
- int rid;
- nullpo_retv(c = va_arg(ap, int*));
- nullpo_retv(name = va_arg(ap, const char*));
- rid = va_arg(ap, int);
- if (ev /* && !ev->nd->src_id */) // Do not run on duplicates. [Paradox924X]
- {
- if(rid) { // a player may only have 1 script running at the same time
- char buf[EVENT_NAME_LENGTH];
- snprintf(buf, ARRAYLENGTH(buf), "%s::%s", ev->nd->exname, name);
- npc->event_sub(map->id2sd(rid), ev, buf);
- }
- else {
- script->run_npc(ev->nd->u.scr.script, ev->pos, rid, ev->nd->bl.id);
- }
- (*c)++;
- }
- }
- // runs the specified event (supports both single-npc and global events)
- int npc_event_do(const char* name)
- {
- if( name[0] == ':' && name[1] == ':' ) {
- return npc->event_doall(name+2); // skip leading "::"
- }
- else {
- struct event_data *ev = strdb_get(npc->ev_db, name);
- if (ev) {
- script->run_npc(ev->nd->u.scr.script, ev->pos, 0, ev->nd->bl.id);
- return 1;
- }
- }
- return 0;
- }
- // runs the specified event, with a RID attached (global only)
- 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);
- if (label_linkdb == NULL)
- return 0;
- linkdb_foreach(label_linkdb, npc->event_doall_sub, &c, name, rid);
- return c;
- }
- // runs the specified event (global only)
- int npc_event_doall(const char* name)
- {
- return npc->event_doall_id(name, 0);
- }
- /*==========================================
- * Clock event execution
- * OnMinute/OnClock/OnHour/OnDay/OnDDHHMM
- *------------------------------------------*/
- 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;
- char buf[64];
- int c = 0;
- clock = time(NULL);
- t = localtime(&clock);
- if (t->tm_min != ev_tm_b.tm_min ) {
- char* day;
- switch (t->tm_wday) {
- case 0: day = "Sun"; break;
- case 1: day = "Mon"; break;
- case 2: day = "Tue"; break;
- case 3: day = "Wed"; break;
- case 4: day = "Thu"; break;
- case 5: day = "Fri"; break;
- case 6: day = "Sat"; break;
- default:day = ""; break;
- }
- sprintf(buf,"OnMinute%02d",t->tm_min);
- c += npc->event_doall(buf);
- sprintf(buf,"OnClock%02d%02d",t->tm_hour,t->tm_min);
- c += npc->event_doall(buf);
- sprintf(buf,"On%s%02d%02d",day,t->tm_hour,t->tm_min);
- c += npc->event_doall(buf);
- }
- if (t->tm_hour != ev_tm_b.tm_hour) {
- sprintf(buf,"OnHour%02d",t->tm_hour);
- c += npc->event_doall(buf);
- }
- if (t->tm_mday != ev_tm_b.tm_mday) {
- sprintf(buf,"OnDay%02d%02d",t->tm_mon+1,t->tm_mday);
- c += npc->event_doall(buf);
- }
- memcpy(&ev_tm_b,t,sizeof(ev_tm_b));
- return c;
- }
- /**
- * OnInit event execution (the start of the event and watch)
- * @param reload Is the server reloading?
- **/
- 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"));
- // This interval has already been added on startup
- if( !reload )
- timer->add_interval(timer->gettick()+100,npc->event_do_clock,0,0,1000);
- }
- /*==========================================
- * Incorporation of the label for the timer event
- * called from npc_parse_script
- *------------------------------------------*/
- int npc_timerevent_export(struct npc_data *nd, int i)
- {
- int t = 0, len = 0;
- char *lname = nd->u.scr.label_list[i].name;
- int pos = nd->u.scr.label_list[i].pos;
- if (sscanf(lname, "OnTimer%d%n", &t, &len) == 1 && lname[len] == '\0') {
- // Timer event
- struct npc_timerevent_list *te = nd->u.scr.timer_event;
- int j, k = nd->u.scr.timeramount;
- if (te == NULL)
- te = (struct npc_timerevent_list *)aMalloc(sizeof(struct npc_timerevent_list));
- else
- te = (struct npc_timerevent_list *)aRealloc( te, sizeof(struct npc_timerevent_list) * (k+1) );
- for (j = 0; j < k; j++) {
- if (te[j].timer > t) {
- memmove(te+j+1, te+j, sizeof(struct npc_timerevent_list)*(k-j));
- break;
- }
- }
- te[j].timer = t;
- te[j].pos = pos;
- nd->u.scr.timer_event = te;
- nd->u.scr.timeramount++;
- }
- return 0;
- }
- struct timer_event_data {
- int rid; //Attached player for this timer.
- int next; //timer index (starts with 0, then goes up to nd->u.scr.timeramount)
- int time; //holds total time elapsed for the script from when timer was started to when last time the event triggered.
- };
- /*==========================================
- * triger 'OnTimerXXXX' events
- *------------------------------------------*/
- int npc_timerevent(int tid, int64 tick, int id, intptr_t data)
- {
- int old_rid, old_timer;
- int64 old_tick;
- struct npc_data *nd = map->id2nd(id);
- struct npc_timerevent_list *te;
- struct timer_event_data *ted = (struct timer_event_data*)data;
- struct map_session_data *sd=NULL;
- if( nd == NULL ) {
- ShowError("npc_timerevent: NPC not found??\n");
- return 0;
- }
- if (ted->rid && (sd = map->id2sd(ted->rid)) == NULL) {
- ShowError("npc_timerevent: Attached player not found.\n");
- ers_free(npc->timer_event_ers, ted);
- return 0;
- }
- // These stuffs might need to be restored.
- old_rid = nd->u.scr.rid;
- old_tick = nd->u.scr.timertick;
- old_timer = nd->u.scr.timer;
- // Set the values of the timer
- nd->u.scr.rid = sd?sd->bl.id:0; //attached rid
- nd->u.scr.timertick = tick; //current time tick
- nd->u.scr.timer = ted->time; //total time from beginning to now
- // Locate the event
- te = nd->u.scr.timer_event + ted->next;
- // Arrange for the next event
- ted->next++;
- if( nd->u.scr.timeramount > ted->next )
- {
- int next = nd->u.scr.timer_event[ ted->next ].timer - nd->u.scr.timer_event[ ted->next - 1 ].timer;
- ted->time += next;
- if( sd )
- sd->npc_timer_id = timer->add(tick+next,npc->timerevent,id,(intptr_t)ted);
- else
- nd->u.scr.timerid = timer->add(tick+next,npc->timerevent,id,(intptr_t)ted);
- }
- else
- {
- if( sd )
- sd->npc_timer_id = INVALID_TIMER;
- else
- nd->u.scr.timerid = INVALID_TIMER;
- ers_free(npc->timer_event_ers, ted);
- }
- // Run the script
- script->run_npc(nd->u.scr.script,te->pos,nd->u.scr.rid,nd->bl.id);
- nd->u.scr.rid = old_rid; // Attached-rid should be restored anyway.
- if( sd )
- { // Restore previous data, only if this timer is a player-attached one.
- nd->u.scr.timer = old_timer;
- nd->u.scr.timertick = old_tick;
- }
- return 0;
- }
- /*==========================================
- * Start/Resume NPC timer
- *------------------------------------------*/
- 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.
- nullpo_ret(nd);
- // Check if there is an OnTimer Event
- ARR_FIND( 0, nd->u.scr.timeramount, j, nd->u.scr.timer_event[j].timer > nd->u.scr.timer );
- if (nd->u.scr.rid > 0 && (sd = map->id2sd(nd->u.scr.rid)) == NULL) {
- // Failed to attach timer to this player.
- ShowError("npc_timerevent_start: Attached player not found!\n");
- return 1;
- }
- // Check if timer is already started.
- if( sd ) {
- if( sd->npc_timer_id != INVALID_TIMER )
- return 0;
- } else if( nd->u.scr.timerid != INVALID_TIMER || nd->u.scr.timertick )
- return 0;
- if (j < nd->u.scr.timeramount) {
- int next;
- struct timer_event_data *ted;
- // Arrange for the next event
- ted = ers_alloc(npc->timer_event_ers, struct timer_event_data);
- ted->next = j; // Set event index
- ted->time = nd->u.scr.timer_event[j].timer;
- next = nd->u.scr.timer_event[j].timer - nd->u.scr.timer;
- if( sd )
- {
- ted->rid = sd->bl.id; // Attach only the player if attachplayerrid was used.
- sd->npc_timer_id = timer->add(tick+next,npc->timerevent,nd->bl.id,(intptr_t)ted);
- }
- else
- {
- ted->rid = 0;
- nd->u.scr.timertick = tick; // Set when timer is started
- nd->u.scr.timerid = timer->add(tick+next,npc->timerevent,nd->bl.id,(intptr_t)ted);
- }
- } else if (!sd) {
- nd->u.scr.timertick = tick;
- }
- return 0;
- }
- /*==========================================
- * Stop NPC timer
- *------------------------------------------*/
- int npc_timerevent_stop(struct npc_data* nd)
- {
- struct map_session_data *sd = NULL;
- int *tid;
- nullpo_ret(nd);
- if (nd->u.scr.rid && (sd = map->id2sd(nd->u.scr.rid)) == NULL) {
- ShowError("npc_timerevent_stop: Attached player not found!\n");
- return 1;
- }
- tid = sd?&sd->npc_timer_id:&nd->u.scr.timerid;
- if( *tid == INVALID_TIMER && (sd || !nd->u.scr.timertick) ) // Nothing to stop
- return 0;
- // Delete timer
- if (*tid != INVALID_TIMER) {
- const struct TimerData *td = timer->get(*tid);
- if (td && td->data)
- ers_free(npc->timer_event_ers, (void*)td->data);
- timer->delete(*tid,npc->timerevent);
- *tid = INVALID_TIMER;
- }
- if (!sd && nd->u.scr.timertick) {
- nd->u.scr.timer += DIFF_TICK32(timer->gettick(),nd->u.scr.timertick); // Set 'timer' to the time that has passed since the beginning of the timers
- nd->u.scr.timertick = 0; // Set 'tick' to zero so that we know it's off.
- }
- return 0;
- }
- /*==========================================
- * Aborts a running NPC timer that is attached to a player.
- *------------------------------------------*/
- void npc_timerevent_quit(struct map_session_data* sd)
- {
- const struct TimerData *td;
- struct npc_data* nd;
- struct timer_event_data *ted;
- // Check timer existence
- if( sd->npc_timer_id == INVALID_TIMER )
- return;
- if( !(td = timer->get(sd->npc_timer_id)) )
- {
- sd->npc_timer_id = INVALID_TIMER;
- return;
- }
- // Delete timer
- nd = map->id2nd(td->id);
- ted = (struct timer_event_data*)td->data;
- timer->delete(sd->npc_timer_id, npc->timerevent);
- sd->npc_timer_id = INVALID_TIMER;
- // Execute OnTimerQuit
- if (nd != NULL) {
- char buf[EVENT_NAME_LENGTH];
- struct event_data *ev;
- snprintf(buf, ARRAYLENGTH(buf), "%s::OnTimerQuit", nd->exname);
- ev = (struct event_data*)strdb_get(npc->ev_db, buf);
- if( ev && ev->nd != nd )
- {
- ShowWarning("npc_timerevent_quit: Unable to execute \"OnTimerQuit\", two NPCs have the same event name [%s]!\n",buf);
- ev = NULL;
- }
- if( ev )
- {
- int old_rid,old_timer;
- int64 old_tick;
- //Set timer related info.
- old_rid = (nd->u.scr.rid == sd->bl.id ? 0 : nd->u.scr.rid); // Detach rid if the last attached player logged off.
- old_tick = nd->u.scr.timertick;
- old_timer = nd->u.scr.timer;
- nd->u.scr.rid = sd->bl.id;
- nd->u.scr.timertick = timer->gettick();
- nd->u.scr.timer = ted->time;
- //Execute label
- script->run_npc(nd->u.scr.script,ev->pos,sd->bl.id,nd->bl.id);
- //Restore previous data.
- nd->u.scr.rid = old_rid;
- nd->u.scr.timer = old_timer;
- nd->u.scr.timertick = old_tick;
- }
- }
- ers_free(npc->timer_event_ers, ted);
- }
- /*==========================================
- * Get the tick value of an NPC timer
- * If it's stopped, return stopped time
- *------------------------------------------*/
- int64 npc_gettimerevent_tick(struct npc_data* nd) {
- int64 tick;
- nullpo_ret(nd);
- // TODO: Get player attached timer's tick. Now we can just get it by using 'getnpctimer' inside OnTimer event.
- tick = nd->u.scr.timer; // The last time it's active(start, stop or event trigger)
- if( nd->u.scr.timertick ) // It's a running timer
- tick += DIFF_TICK(timer->gettick(), nd->u.scr.timertick);
- return tick;
- }
- /*==========================================
- * Set tick for running and stopped timer
- *------------------------------------------*/
- int npc_settimerevent_tick(struct npc_data* nd, int newtimer)
- {
- bool flag;
- int old_rid;
- //struct map_session_data *sd = NULL;
- nullpo_ret(nd);
- // TODO: Set player attached timer's tick.
- old_rid = nd->u.scr.rid;
- nd->u.scr.rid = 0;
- // Check if timer is started
- flag = (nd->u.scr.timerid != INVALID_TIMER || nd->u.scr.timertick);
- if( flag ) npc->timerevent_stop(nd);
- nd->u.scr.timer = newtimer;
- if( flag ) npc->timerevent_start(nd, -1);
- nd->u.scr.rid = old_rid;
- return 0;
- }
- int npc_event_sub(struct map_session_data* sd, struct event_data* ev, const char* eventname)
- {
- if ( sd->npc_id != 0 )
- {
- //Enqueue the event trigger.
- int i;
- ARR_FIND( 0, MAX_EVENTQUEUE, i, sd->eventqueue[i][0] == '\0' );
- if( i < MAX_EVENTQUEUE )
- {
- safestrncpy(sd->eventqueue[i],eventname,EVENT_NAME_LENGTH); //Event enqueued.
- return 0;
- }
- ShowWarning("npc_event: player's event queue is full, can't add event '%s' !\n", eventname);
- return 1;
- }
- if( ev->nd->option&OPTION_INVISIBLE )
- {
- //Disabled npc, shouldn't trigger event.
- npc->event_dequeue(sd);
- return 2;
- }
- script->run_npc(ev->nd->u.scr.script,ev->pos,sd->bl.id,ev->nd->bl.id);
- return 0;
- }
- /*==========================================
- * NPC processing event type
- *------------------------------------------*/
- 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;
- nullpo_ret(sd);
- if( ev == NULL || (nd = ev->nd) == NULL ) {
- if( !ontouch )
- ShowError("npc_event: event not found [%s]\n", eventname);
- return ontouch;
- }
- switch(ontouch) {
- case 1:
- nd->touching_id = sd->bl.id;
- sd->touching_id = nd->bl.id;
- break;
- case 2:
- sd->areanpc_id = nd->bl.id;
- break;
- }
- return npc->event_sub(sd,ev,eventname);
- }
- /*==========================================
- * Sub chk then execute area event type
- *------------------------------------------*/
- int npc_touch_areanpc_sub(struct block_list *bl, va_list ap) {
- struct map_session_data *sd;
- int pc_id;
- char *name;
- nullpo_ret(bl);
- nullpo_ret((sd = map->id2sd(bl->id)));
- pc_id = va_arg(ap,int);
- name = va_arg(ap,char*);
- if( sd->state.warping )
- return 0;
- if( pc_ishiding(sd) )
- return 0;
- if( pc_id == sd->bl.id )
- return 0;
- npc->event(sd,name,1);
- return 1;
- }
- /*==========================================
- * 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) {
- struct npc_data *nd = map->id2nd(sd->touching_id);
- short xs, ys;
- if( !nd || nd->touching_id != sd->bl.id )
- return 1;
- xs = nd->u.scr.xs;
- ys = nd->u.scr.ys;
- if( sd->bl.m != nd->bl.m ||
- sd->bl.x < nd->bl.x - xs || sd->bl.x > nd->bl.x + xs ||
- sd->bl.y < nd->bl.y - ys || sd->bl.y > nd->bl.y + ys ||
- pc_ishiding(sd) || leavemap )
- {
- char name[EVENT_NAME_LENGTH];
- nd->touching_id = sd->touching_id = 0;
- snprintf(name, ARRAYLENGTH(name), "%s::%s", nd->exname, script->config.ontouch_name);
- map->forcountinarea(npc->touch_areanpc_sub,nd->bl.m,nd->bl.x - xs,nd->bl.y - ys,nd->bl.x + xs,nd->bl.y + ys,1,BL_PC,sd->bl.id,name);
- }
- return 0;
- }
- /*==========================================
- * 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)
- {
- int xs,ys;
- int f = 1;
- int i;
- int j, found_warp = 0;
- nullpo_retr(1, sd);
- #if 0 // Why not enqueue it? [Inkfish]
- if(sd->npc_id)
- return 1;
- #endif // 0
- for(i=0;i<map->list[m].npc_num;i++) {
- if (map->list[m].npc[i]->option&OPTION_INVISIBLE) {
- f=0; // a npc was found, but it is disabled; don't print warning
- continue;
- }
- switch(map->list[m].npc[i]->subtype) {
- case WARP:
- xs=map->list[m].npc[i]->u.warp.xs;
- ys=map->list[m].npc[i]->u.warp.ys;
- break;
- case SCRIPT:
- xs=map->list[m].npc[i]->u.scr.xs;
- ys=map->list[m].npc[i]->u.scr.ys;
- break;
- default:
- continue;
- }
- if( x >= map->list[m].npc[i]->bl.x-xs && x <= map->list[m].npc[i]->bl.x+xs
- && y >= map->list[m].npc[i]->bl.y-ys && y <= map->list[m].npc[i]->bl.y+ys )
- break;
- }
- if( i == map->list[m].npc_num ) {
- if( f == 1 ) // no npc found
- ShowError("npc_touch_areanpc : stray NPC cell/NPC not found in the block on coordinates '%s',%d,%d\n", map->list[m].name, x, y);
- return 1;
- }
- switch(map->list[m].npc[i]->subtype) {
- case WARP:
- if( pc_ishiding(sd) || (sd->sc.count && sd->sc.data[SC_CAMOUFLAGE]) )
- break; // hidden chars cannot use warps
- pc->setpos(sd,map->list[m].npc[i]->u.warp.mapindex,map->list[m].npc[i]->u.warp.x,map->list[m].npc[i]->u.warp.y,CLR_OUTSIGHT);
- break;
- case SCRIPT:
- for (j = i; j < map->list[m].npc_num; j++) {
- if (map->list[m].npc[j]->subtype != WARP) {
- continue;
- }
- if ((sd->bl.x >= (map->list[m].npc[j]->bl.x - map->list[m].npc[j]->u.warp.xs)
- && sd->bl.x <= (map->list[m].npc[j]->bl.x + map->list[m].npc[j]->u.warp.xs))
- && (sd->bl.y >= (map->list[m].npc[j]->bl.y - map->list[m].npc[j]->u.warp.ys)
- && sd->bl.y <= (map->list[m].npc[j]->bl.y + map->list[m].npc[j]->u.warp.ys))
- ) {
- if( pc_ishiding(sd) || (sd->sc.count && sd->sc.data[SC_CAMOUFLAGE]) )
- break; // hidden chars cannot use warps
- pc->setpos(sd,map->list[m].npc[j]->u.warp.mapindex,map->list[m].npc[j]->u.warp.x,map->list[m].npc[j]->u.warp.y,CLR_OUTSIGHT);
- found_warp = 1;
- break;
- }
- }
- if (found_warp > 0) {
- break;
- }
- if( npc->ontouch_event(sd,map->list[m].npc[i]) > 0 && npc->ontouch2_event(sd,map->list[m].npc[i]) > 0 )
- { // failed to run OnTouch event, so just click the npc
- struct unit_data *ud = unit->bl2ud(&sd->bl);
- if( ud && ud->walkpath.path_pos < ud->walkpath.path_len )
- { // Since walktimer always == INVALID_TIMER at this time, we stop walking manually. [Inkfish]
- clif->fixpos(&sd->bl);
- ud->walkpath.path_pos = ud->walkpath.path_len;
- }
- sd->areanpc_id = map->list[m].npc[i]->bl.id;
- npc->click(sd,map->list[m].npc[i]);
- }
- break;
- }
- return 0;
- }
- /*==========================================
- * 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)
- {
- struct npc_data *nd = NULL;
- nullpo_retr(1, sd);
- if (!sd->areanpc_id)
- return 0;
- nd = map->id2nd(sd->areanpc_id);
- if (nd == NULL) {
- sd->areanpc_id = 0;
- return 1;
- }
- npc->onuntouch_event(sd, nd);
- sd->areanpc_id = 0;
- return 0;
- }
- // OnTouch NPC or Warp for Mobs
- // Return 1 if Warped
- int npc_touch_areanpc2(struct mob_data *md)
- {
- int i, m = md->bl.m, x = md->bl.x, y = md->bl.y, id;
- char eventname[EVENT_NAME_LENGTH];
- struct event_data* ev;
- int xs, ys;
- for( i = 0; i < map->list[m].npc_num; i++ ) {
- if( map->list[m].npc[i]->option&OPTION_INVISIBLE )
- continue;
- switch( map->list[m].npc[i]->subtype ) {
- case WARP:
- if( !( battle_config.mob_warp&1 ) )
- continue;
- xs = map->list[m].npc[i]->u.warp.xs;
- ys = map->list[m].npc[i]->u.warp.ys;
- break;
- case SCRIPT:
- xs = map->list[m].npc[i]->u.scr.xs;
- ys = map->list[m].npc[i]->u.scr.ys;
- break;
- default:
- continue; // Keep Searching
- }
- if( x >= map->list[m].npc[i]->bl.x-xs && x <= map->list[m].npc[i]->bl.x+xs && y >= map->list[m].npc[i]->bl.y-ys && y <= map->list[m].npc[i]->bl.y+ys ) {
- // In the npc touch area
- switch( map->list[m].npc[i]->subtype ) {
- case WARP:
- xs = map->mapindex2mapid(map->list[m].npc[i]->u.warp.mapindex);
- if( m < 0 )
- break; // Cannot Warp between map servers
- if( unit->warp(&md->bl, xs, map->list[m].npc[i]->u.warp.x, map->list[m].npc[i]->u.warp.y, CLR_OUTSIGHT) == 0 )
- return 1; // Warped
- break;
- case SCRIPT:
- if( map->list[m].npc[i]->bl.id == md->areanpc_id )
- break; // Already touch this NPC
- snprintf(eventname, ARRAYLENGTH(eventname), "%s::OnTouchNPC", map->list[m].npc[i]->exname);
- if( (ev = (struct event_data*)strdb_get(npc->ev_db, eventname)) == NULL || ev->nd == NULL )
- break; // No OnTouchNPC Event
- md->areanpc_id = map->list[m].npc[i]->bl.id;
- id = md->bl.id; // Stores Unique ID
- script->run_npc(ev->nd->u.scr.script, ev->pos, md->bl.id, ev->nd->bl.id);
- if( map->id2md(id) == NULL ) return 1; // Not Warped, but killed
- break;
- }
- return 0;
- }
- }
- return 0;
- }
- //Checks if there are any NPC on-touch objects on the given range.
- //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) {
- int i;
- int x0,y0,x1,y1;
- int xs,ys;
- if (range < 0) return 0;
- x0 = max(x-range, 0);
- y0 = max(y-range, 0);
- x1 = min(x+range, map->list[m].xs-1);
- y1 = min(y+range, map->list[m].ys-1);
- //First check for npc_cells on the range given
- i = 0;
- for (ys = y0; ys <= y1 && !i; ys++) {
- for(xs = x0; xs <= x1 && !i; xs++) {
- if (map->getcell(m, NULL, xs, ys, CELL_CHKNPC))
- i = 1;
- }
- }
- if (!i) return 0; //No NPC_CELLs.
- //Now check for the actual NPC on said range.
- for(i=0;i<map->list[m].npc_num;i++) {
- if (map->list[m].npc[i]->option&OPTION_INVISIBLE)
- continue;
- switch(map->list[m].npc[i]->subtype) {
- case WARP:
- if (!(flag&1))
- continue;
- xs=map->list[m].npc[i]->u.warp.xs;
- ys=map->list[m].npc[i]->u.warp.ys;
- break;
- case SCRIPT:
- if (!(flag&2))
- continue;
- xs=map->list[m].npc[i]->u.scr.xs;
- ys=map->list[m].npc[i]->u.scr.ys;
- break;
- default:
- continue;
- }
- if( x1 >= map->list[m].npc[i]->bl.x-xs && x0 <= map->list[m].npc[i]->bl.x+xs
- && y1 >= map->list[m].npc[i]->bl.y-ys && y0 <= map->list[m].npc[i]->bl.y+ys )
- break; // found a npc
- }
- if (i==map->list[m].npc_num)
- return 0;
- return (map->list[m].npc[i]->bl.id);
- }
- /*==========================================
- * 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)
- {
- struct npc_data *nd = BL_CAST(BL_NPC, bl);
- int distance = AREA_SIZE + 1;
- nullpo_retr(NULL, sd);
- if (nd == NULL)
- return NULL;
- if (sd->npc_id == bl->id)
- return nd;
- if (nd->class_<0) //Class-less npc, enable click from anywhere.
- return nd;
- if (distance > nd->area_size)
- distance = nd->area_size;
- if (bl->m != sd->bl.m ||
- bl->x < sd->bl.x - distance || bl->x > sd->bl.x + distance ||
- bl->y < sd->bl.y - distance || bl->y > sd->bl.y + distance)
- {
- return NULL;
- }
- return nd;
- }
- /*==========================================
- * Make NPC talk in global chat (like npctalk)
- *------------------------------------------*/
- int npc_globalmessage(const char* name, const char* mes)
- {
- struct npc_data* nd = npc->name2id(name);
- char temp[100];
- if (!nd)
- return 0;
- snprintf(temp, sizeof(temp), "%s : %s", name, mes);
- clif->GlobalMessage(&nd->bl,temp);
- return 0;
- }
- // MvP tomb [GreenBox]
- void run_tomb(struct map_session_data* sd, struct npc_data* nd) {
- char buffer[200];
- char time[10];
- strftime(time, sizeof(time), "%H:%M", localtime(&nd->u.tomb.kill_time));
- // TODO: Find exact color?
- snprintf(buffer, sizeof(buffer), msg_sd(sd,857), nd->u.tomb.md->db->name); // "[ ^EE0000%s^000000 ]"
- clif->scriptmes(sd, nd->bl.id, buffer);
- clif->scriptmes(sd, nd->bl.id, msg_sd(sd,858)); // "Has met its demise"
- snprintf(buffer, sizeof(buffer), msg_sd(sd,859), time); // "Time of death : ^EE0000%s^000000"
- clif->scriptmes(sd, nd->bl.id, buffer);
- clif->scriptmes(sd, nd->bl.id, msg_sd(sd,860)); // "Defeated by"
- snprintf(buffer, sizeof(buffer), msg_sd(sd,861), nd->u.tomb.killer_name[0] ? nd->u.tomb.killer_name : msg_sd(sd,15)); // "[^EE0000%s^000000]" / "Unknown"
- clif->scriptmes(sd, nd->bl.id, buffer);
- clif->scriptclose(sd, nd->bl.id);
- }
- /*==========================================
- * 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)
- {
- nullpo_retr(1, sd);
- // This usually happens when the player clicked on a NPC that has the view id
- // of a mob, to activate this kind of npc it's needed to be in a 2,2 range
- // from it. If the OnTouch area of a npc, coincides with the 2,2 range of
- // another it's expected that the OnTouch event be put first in stack, because
- // unit_walktoxy_timer is executed before any other function in this case.
- // So it's best practice to put an 'end;' before OnTouch events in npcs that
- // have view ids of mobs to avoid this "issue" [Panikon]
- if (sd->npc_id != 0) {
- // The player clicked a npc after entering an OnTouch area
- if( sd->areanpc_id != sd->npc_id )
- ShowError("npc_click: npc_id != 0\n");
- return 1;
- }
- if( !nd )
- return 1;
- if ((nd = npc->checknear(sd,&nd->bl)) == NULL)
- return 1;
- //Hidden/Disabled npc.
- if (nd->class_ < 0 || nd->option&(OPTION_INVISIBLE|OPTION_HIDE))
- return 1;
- switch(nd->subtype) {
- case SHOP:
- clif->npcbuysell(sd,nd->bl.id);
- break;
- case CASHSHOP:
- clif->cashshop_show(sd,nd);
- break;
- case SCRIPT:
- if( nd->u.scr.shop && nd->u.scr.shop->items && nd->u.scr.trader ) {
- if( !npc->trader_open(sd,nd) )
- return 1;
- } else
- script->run_npc(nd->u.scr.script,0,sd->bl.id,nd->bl.id);
- break;
- case TOMB:
- npc->run_tomb(sd,nd);
- break;
- }
- return 0;
- }
- /*==========================================
- *
- *------------------------------------------*/
- int npc_scriptcont(struct map_session_data* sd, int id, bool closing) {
- struct block_list *target = map->id2bl(id);
- 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;
- }
- if(id != npc->fake_nd->bl.id) { // Not item script
- 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();
- #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 )
- return 1;
- if( !sd->st )
- return 1;
- if( closing && sd->st->state == CLOSE )
- sd->st->state = END;
- script->run_main(sd->st);
- return 0;
- }
- /*==========================================
- * Chk if valid call then open buy or selling list
- *------------------------------------------*/
- int npc_buysellsel(struct map_session_data* sd, int id, int type) {
- struct npc_data *nd;
- nullpo_retr(1, sd);
- if ((nd = npc->checknear(sd,map->id2bl(id))) == NULL)
- return 1;
- if ( nd->subtype != SHOP && !(nd->subtype == SCRIPT && nd->u.scr.shop && nd->u.scr.shop->items) ) {
- if( nd->subtype == SCRIPT )
- ShowError("npc_buysellsel: trader '%s' has no shop list!\n",nd->exname);
- else
- ShowError("npc_buysellsel: no such shop npc %d (%s)\n",id,nd->exname);
- if (sd->npc_id == id)
- sd->npc_id = 0;
- return 1;
- }
- if (nd->option & OPTION_INVISIBLE) // can't buy if npc is not visible (hack?)
- return 1;
- if( nd->class_ < 0 && !sd->state.callshop ) {// not called through a script and is not a visible NPC so an invalid call
- return 1;
- }
- // reset the callshop state for future calls
- sd->state.callshop = 0;
- sd->npc_shopid = id;
- if (type==0) {
- clif->buylist(sd,nd);
- } else {
- clif->selllist(sd);
- }
- return 0;
- }
- /*==========================================
- * Cash Shop Buy List
- *------------------------------------------*/
- int npc_cashshop_buylist(struct map_session_data *sd, int points, int count, unsigned short* item_list) {
- int i, j, nameid, amount, new_, w, vt;
- struct npc_data *nd = NULL;
- struct npc_item_list *shop = NULL;
- unsigned short shop_size = 0;
- if( sd->state.trading )
- return ERROR_TYPE_EXCHANGE;
- if( count <= 0 )
- return ERROR_TYPE_ITEM_ID;
- if( points < 0 )
- return ERROR_TYPE_MONEY;
- nd = map->id2nd(sd->npc_shopid);
- if (nd == NULL)
- 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 ) {
- shop = nd->u.scr.shop->item;
- shop_size = nd->u.scr.shop->items;
- } else
- return ERROR_TYPE_NPC;
- } else {
- shop = nd->u.shop.shop_item;
- shop_size = nd->u.shop.count;
- }
- new_ = 0;
- w = 0;
- vt = 0; // Global Value
- // Validating Process ----------------------------------------------------
- for( i = 0; i < count; i++ ) {
- nameid = item_list[i*2+1];
- amount = item_list[i*2+0];
- if( !itemdb->exists(nameid) || amount <= 0 )
- return ERROR_TYPE_ITEM_ID;
- ARR_FIND(0,shop_size,j,shop[j].nameid == nameid);
- if( j == shop_size || shop[j].value <= 0 )
- return ERROR_TYPE_ITEM_ID;
- if( !itemdb->isstackable(nameid) && amount > 1 ) {
- 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, amount, nameid);
- amount = item_list[i*2+0] = 1;
- }
- switch( pc->checkadditem(sd,nameid,amount) ) {
- case ADDITEM_NEW:
- new_++;
- break;
- case ADDITEM_OVERAMOUNT:
- return ERROR_TYPE_INVENTORY_WEIGHT;
- }
- vt += shop[j].value * amount;
- w += itemdb_weight(nameid) * amount;
- }
- if( w + sd->weight > sd->max_weight )
- return ERROR_TYPE_INVENTORY_WEIGHT;
- if( pc->inventoryblank(sd) < new_ )
- return ERROR_TYPE_INVENTORY_WEIGHT;
- if( points > vt ) points = vt;
- // Payment Process ----------------------------------------------------
- if( nd->subtype == SCRIPT && nd->u.scr.shop->type == NST_CUSTOM ) {
- if( !npc->trader_pay(nd,sd,vt,points) )
- return ERROR_TYPE_MONEY;
- } else {
- if( sd->kafraPoints < points || sd->cashPoints < (vt - points) )
- return ERROR_TYPE_MONEY;
- pc->paycash(sd,vt,points);
- }
- // Delivery Process ----------------------------------------------------
- for( i = 0; i < count; i++ ) {
- struct item item_tmp;
- nameid = item_list[i*2+1];
- amount = item_list[i*2+0];
- memset(&item_tmp,0,sizeof(item_tmp));
- if( !pet->create_egg(sd,nameid) ) {
- item_tmp.nameid = nameid;
- item_tmp.identify = 1;
- pc->additem(sd,&item_tmp,amount,LOG_TYPE_NPC);
- }
- }
- return ERROR_TYPE_NONE;
- }
- //npc_buylist for script-controlled shops.
- int npc_buylist_sub(struct map_session_data* sd, int n, unsigned short* item_list, struct npc_data* nd)
- {
- char npc_ev[EVENT_NAME_LENGTH];
- int i;
- int key_nameid = 0;
- int key_amount = 0;
- // discard old contents
- script->cleararray_pc(sd, "@bought_nameid", (void*)0);
- script->cleararray_pc(sd, "@bought_quantity", (void*)0);
- // save list of bought items
- for( i = 0; i < n; i++ ) {
- script->setarray_pc(sd, "@bought_nameid", i, (void*)(intptr_t)item_list[i*2+1], &key_nameid);
- script->setarray_pc(sd, "@bought_quantity", i, (void*)(intptr_t)item_list[i*2], &key_amount);
- }
- // invoke event
- snprintf(npc_ev, ARRAYLENGTH(npc_ev), "%s::OnBuyItem", nd->exname);
- npc->event(sd, npc_ev, 0);
- return 0;
- }
- /**
- * Loads persistent NPC Market Data from SQL
- **/
- void npc_market_fromsql(void) {
- SqlStmt* stmt = SQL->StmtMalloc(map->mysql_handle);
- char name[NAME_LENGTH+1];
- int itemid;
- int amount;
- if ( SQL_ERROR == SQL->StmtPrepare(stmt, "SELECT `name`, `itemid`, `amount` FROM `%s`", map->npc_market_data_db)
- || SQL_ERROR == SQL->StmtExecute(stmt)
- ) {
- SqlStmt_ShowDebug(stmt);
- SQL->StmtFree(stmt);
- return;
- }
- SQL->StmtBindColumn(stmt, 0, SQLDT_STRING, &name[0], sizeof(name), NULL, NULL);
- SQL->StmtBindColumn(stmt, 1, SQLDT_INT, &itemid, 0, NULL, NULL);
- SQL->StmtBindColumn(stmt, 2, SQLDT_INT, &amount, 0, NULL, NULL);
- while ( SQL_SUCCESS == SQL->StmtNextRow(stmt) ) {
- struct npc_data *nd = NULL;
- unsigned short i;
- if( !(nd = npc->name2id(name)) ) {
- ShowError("npc_market_fromsql: NPC '%s' not found! skipping...\n",name);
- npc->market_delfromsql_sub(name, USHRT_MAX);
- continue;
- } else if ( nd->subtype != SCRIPT || !nd->u.scr.shop || !nd->u.scr.shop->items || nd->u.scr.shop->type != NST_MARKET ) {
- ShowError("npc_market_fromsql: NPC '%s' is not proper for market, skipping...\n",name);
- npc->market_delfromsql_sub(name, USHRT_MAX);
- continue;
- }
- for(i = 0; i < nd->u.scr.shop->items; i++) {
- if( nd->u.scr.shop->item[i].nameid == itemid ) {
- nd->u.scr.shop->item[i].qty = amount;
- break;
- }
- }
- if( i == nd->u.scr.shop->items ) {
- ShowError("npc_market_fromsql: NPC '%s' does not sell item %d (qty %d), deleting...\n",name,itemid,amount);
- npc->market_delfromsql_sub(name, itemid);
- continue;
- }
- }
- SQL->StmtFree(stmt);
- }
- /**
- * Saves persistent NPC Market Data into SQL
- **/
- void npc_market_tosql(struct npc_data *nd, unsigned short index) {
- 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 ) {
- 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 {
- if( SQL_ERROR == SQL->Query(map->mysql_handle, "DELETE FROM `%s` WHERE `name`='%s' AND `itemid`='%d' LIMIT 1",
- map->npc_market_data_db, npcname, index) )
- Sql_ShowDebug(map->mysql_handle);
- }
- }
- /**
- * Removes persistent NPC Market Data from SQL
- **/
- void npc_market_delfromsql(struct npc_data *nd, unsigned short index) {
- npc->market_delfromsql_sub(nd->exname, index == USHRT_MAX ? index : nd->u.scr.shop->item[index].nameid);
- }
- /**
- * Judges whether to allow and spawn a trader's window.
- **/
- bool npc_trader_open(struct map_session_data *sd, struct npc_data *nd) {
- if( !nd->u.scr.shop || !nd->u.scr.shop->items )
- return false;
- switch( nd->u.scr.shop->type ) {
- case NST_ZENY:
- sd->state.callshop = 1;
- clif->npcbuysell(sd,nd->bl.id);
- return true;/* we skip sd->npc_shopid, npc->buysell will set it then when the player selects */
- case NST_MARKET: {
- unsigned short i;
- for(i = 0; i < nd->u.scr.shop->items; i++) {
- if( nd->u.scr.shop->item[i].qty )
- break;
- }
- /* nothing to display, no items available */
- if (i == nd->u.scr.shop->items) {
- clif->messagecolor_self(sd->fd, COLOR_RED, msg_sd(sd,881));
- return false;
- }
- clif->npc_market_open(sd,nd);
- }
- break;
- default:
- clif->cashshop_show(sd,nd);
- break;
- }
- sd->npc_shopid = nd->bl.id;
- return true;
- }
- /**
- * Creates (npc_data)->u.scr.shop and updates all duplicates across the server to match the created pointer
- *
- * @param master id of the original npc
- **/
- void npc_trader_update(int master) {
- DBIterator* iter;
- struct block_list* bl;
- struct npc_data *master_nd = map->id2nd(master);
- CREATE(master_nd->u.scr.shop,struct npc_shop_data,1);
- iter = db_iterator(map->id_db);
- for (bl = dbi_first(iter); dbi_exists(iter); bl = dbi_next(iter)) {
- if (bl->type == BL_NPC) {
- struct npc_data *nd = BL_UCAST(BL_NPC, bl);
- if (nd->src_id == master) {
- nd->u.scr.shop = master_nd->u.scr.shop;
- }
- }
- }
- dbi_destroy(iter);
- }
- /**
- * Tries to issue a CountFunds event to the shop.
- *
- * @param nd shop
- * @param sd player
- **/
- void npc_trader_count_funds(struct npc_data *nd, struct map_session_data *sd) {
- char evname[EVENT_NAME_LENGTH];
- struct event_data *ev = NULL;
- npc->trader_funds[0] = npc->trader_funds[1] = 0;/* clear */
- switch( nd->u.scr.shop->type ) {
- case NST_CASH:
- npc->trader_funds[0] = sd->cashPoints;
- npc->trader_funds[1] = sd->kafraPoints;
- return;
- case NST_CUSTOM:
- break;
- default:
- ShowError("npc_trader_count_funds: unsupported shop type %d\n",nd->u.scr.shop->type);
- return;
- }
- snprintf(evname, EVENT_NAME_LENGTH, "%s::OnCountFunds",nd->exname);
- if ( (ev = strdb_get(npc->ev_db, evname)) )
- script->run_npc(ev->nd->u.scr.script, ev->pos, sd->bl.id, ev->nd->bl.id);
- else
- ShowError("npc_trader_count_funds: '%s' event '%s' not found, operation failed\n",nd->exname,evname);
- /* the callee will rely on npc->trader_funds, upon success script->run updates them */
- }
- /**
- * Tries to issue a payment to the NPC Event capable of handling it
- *
- * @param nd shop
- * @param sd player
- * @param price total cost
- * @param points the amount input in the shop by the user to use from the secondary currency (if any is being employed)
- *
- * @return bool whether it was successful (if the script does not respond it will fail)
- **/
- bool npc_trader_pay(struct npc_data *nd, struct map_session_data *sd, int price, int points) {
- char evname[EVENT_NAME_LENGTH];
- struct event_data *ev = NULL;
- npc->trader_ok = false;/* clear */
- snprintf(evname, EVENT_NAME_LENGTH, "%s::OnPayFunds",nd->exname);
- if ( (ev = strdb_get(npc->ev_db, evname)) ) {
- pc->setreg(sd,script->add_str("@price"),price);
- pc->setreg(sd,script->add_str("@points"),points);
- script->run_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);
- return npc->trader_ok;/* run script will deal with it */
- }
- /*==========================================
- * Cash Shop Buy
- *------------------------------------------*/
- int npc_cashshop_buy(struct map_session_data *sd, int nameid, int amount, int points) {
- struct npc_data *nd = NULL;
- struct item_data *item;
- struct npc_item_list *shop = NULL;
- int i, price, w;
- unsigned short shop_size = 0;
- if( amount <= 0 )
- return ERROR_TYPE_ITEM_ID;
- if( points < 0 )
- return ERROR_TYPE_MONEY;
- if( sd->state.trading )
- return ERROR_TYPE_EXCHANGE;
- nd = map->id2nd(sd->npc_shopid);
- if (nd == NULL)
- return ERROR_TYPE_NPC;
- if( (item = itemdb->exists(nameid)) == NULL )
- 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 ) {
- shop = nd->u.scr.shop->item;
- shop_size = nd->u.scr.shop->items;
- } else
- return ERROR_TYPE_NPC;
- } else {
- shop = nd->u.shop.shop_item;
- …
Large files files are truncated, but you can click here to view the full file