From 52ed37bd877a4f462171a1501ec1ab2c4fd19eea Mon Sep 17 00:00:00 2001 From: cookiecrumbs Date: Sun, 22 Jul 2012 05:15:32 +0000 Subject: Added the ability to bind atcommands to NPC events (ex: NPCNAME::OnEvent); original version by ToastOfDoom however heavily modified by I enabling command level at the invoking/character (@/#) level and fixes to prevent console errors as well as fixes aimed to ensure compatibility with rAthena. Updated the script_commands.txt documentation with the following script commands: bindatcmd, unbindatcmd and useatcmd. git-svn-id: https://rathena.svn.sourceforge.net/svnroot/rathena/trunk@16471 54d463be-8e91-2dee-dedb-b68131a5f0ec --- doc/script_commands.txt | 22 ++++++++++ src/map/atcommand.c | 42 ++++++++++++++++++- src/map/atcommand.h | 13 ++++++ src/map/npc.c | 66 ++++++++++++++++++++++++++++++ src/map/npc.h | 3 ++ src/map/script.c | 105 ++++++++++++++++++++++++++++++++++++++++++++++++ src/map/script.h | 3 ++ 7 files changed, 253 insertions(+), 1 deletion(-) diff --git a/doc/script_commands.txt b/doc/script_commands.txt index 33df8a59b..d862acd9d 100644 --- a/doc/script_commands.txt +++ b/doc/script_commands.txt @@ -2109,6 +2109,28 @@ array, shifting all the elements beyond this towards the beginning. --------------------------------------- +*bindatcmd "command","{NPC NAME}::"{,atcommand level,charcommand level}; +*bindatcmd ("command","{NPC NAME}::"{,atcommand level,charcommand level}); + +This command will bind a NPC event label to an atcommand. Upon execution of +the atcommand the user will invoke the NPC event label. + +--------------------------------------- + +*unbindatcmd "command"; +*unbindatcmd ("command"); + +This command will unbind a NPC event label from an atcommand. + +--------------------------------------- + +*useatcmd "command"; +*useatcmd ("command"); + +This command will execute a custom atcommand on the attached RID from a script. + +--------------------------------------- + ====================================== |2.- Information-retrieving commands.| ====================================== diff --git a/src/map/atcommand.c b/src/map/atcommand.c index afa22b392..15f75d55a 100644 --- a/src/map/atcommand.c +++ b/src/map/atcommand.c @@ -85,6 +85,17 @@ static AtCommandInfo* get_atcommandinfo_byname(const char *name); // @help static const char* atcommand_checkalias(const char *aliasname); // @help static void atcommand_get_suggestions(struct map_session_data* sd, const char *name, bool atcommand); // @help +// @commands (script-based) +struct Atcmd_Binding* get_atcommandbind_byname(const char* name) +{ + int i = 0; + if( *name == atcommand_symbol || *name == charcommand_symbol ) + name++; // for backwards compatibility + ARR_FIND( 0, ARRAYLENGTH(atcmd_binding), i, strcmp(atcmd_binding[i].command, name) == 0 ); + return ( i < ARRAYLENGTH(atcmd_binding) ) ? &atcmd_binding[i] : NULL; + return NULL; +} + //----------------------------------------------------------- // Return the message string of the specified number by [Yor] //----------------------------------------------------------- @@ -8987,6 +8998,9 @@ bool is_atcommand(const int fd, struct map_session_data* sd, const char* message TBL_PC * ssd = NULL; //sd for target AtCommandInfo * info; + // @commands (script based) + Atcmd_Binding * binding; + nullpo_retr(false, sd); //Shouldn't happen @@ -9063,7 +9077,33 @@ bool is_atcommand(const int fd, struct map_session_data* sd, const char* message //check to see if any params exist within this command if( sscanf(atcmd_msg, "%99s %99[^\n]", command, params) < 2 ) params[0] = '\0'; - + + // @commands (script based) + if(type == 1) { + // Check if the command initiated is a character command + if (*message == charcommand_symbol && + (ssd = map_nick2sd(charname)) == NULL && (ssd = map_nick2sd(charname2)) == NULL ) + { + sprintf(output, "%s failed. Player not found.", command); + clif_displaymessage(fd, output); + return true; + } + + // Get atcommand binding + binding = get_atcommandbind_byname(command); + + // Check if the binding isn't NULL and there is a NPC event, level of usage met, et cetera + if( binding != NULL && binding->npc_event[0] && + ((*atcmd_msg == atcommand_symbol && pc_get_group_level(sd) >= binding->level) || + (*atcmd_msg == charcommand_symbol && pc_get_group_level(sd) >= binding->level2))) + { + // Check if self or character invoking; if self == character invoked, then self invoke. + bool invokeFlag = ((*atcmd_msg == atcommand_symbol) ? 1 : 0); + npc_do_atcmd_event((invokeFlag ? sd : ssd), command, params, binding->npc_event); + return true; + } + } + //Grab the command information and check for the proper GM level required to use it or if the command exists info = get_atcommandinfo_byname(atcommand_checkalias(command + 1)); if (info == NULL) { diff --git a/src/map/atcommand.h b/src/map/atcommand.h index aaf477a97..9bc844664 100644 --- a/src/map/atcommand.h +++ b/src/map/atcommand.h @@ -34,4 +34,17 @@ const char* msg_txt(int msg_number); int msg_config_read(const char* cfgName); void do_final_msg(void); +#define MAX_ATCMD_BINDINGS 100 + +// @commands (script based) +typedef struct Atcmd_Binding { + char command[50]; + char npc_event[50]; + int level; + int level2; +} Atcmd_Binding; + +struct Atcmd_Binding atcmd_binding[MAX_ATCMD_BINDINGS]; +struct Atcmd_Binding* get_atcommandbind_byname(const char* name); + #endif /* _ATCOMMAND_H_ */ diff --git a/src/map/npc.c b/src/map/npc.c index 3d46a6876..d629ac1b3 100644 --- a/src/map/npc.c +++ b/src/map/npc.c @@ -2750,6 +2750,72 @@ void npc_setclass(struct npc_data* nd, short class_) 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) +{ + struct event_data* ev = (struct event_data*)strdb_get(ev_db, eventname); + struct npc_data *nd; + struct script_state *st; + int i = 0, j = 0, k = 0; + char *temp; + temp = (char*)aMalloc(strlen(message) + 1); + + nullpo_ret(sd); + + if( ev == NULL || (nd = ev->nd) == NULL ) + { + ShowError("npc_event: event not found [%s]\n", eventname); + return 0; + } + + 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,50); //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->sc.option&OPTION_INVISIBLE ) + { // Disabled npc, shouldn't trigger event. + npc_event_dequeue(sd); + return 2; + } + + st = script_alloc_state(ev->nd->u.scr.script, ev->pos, sd->bl.id, ev->nd->bl.id); + setd_sub(st, NULL, ".@atcmd_command$", 0, (void *)command, NULL); + + // split atcmd parameters based on spaces + i = 0; + j = 0; + while( message[i] != '\0' ) + { + if( message[i] == ' ' && k < 127 ) + { + temp[j] = '\0'; + setd_sub(st, NULL, ".@atcmd_parameters$", k++, (void *)temp, NULL); + j = 0; + ++i; + } + else + temp[j++] = message[i++]; + } + + temp[j] = '\0'; + setd_sub(st, NULL, ".@atcmd_parameters$", k++, (void *)temp, NULL); + setd_sub(st, NULL, ".@atcmd_numparameters", 0, (void *)&k, NULL); + aFree(temp); + + run_script_main(st); + return 0; +} + /// Parses a function. /// function%TAB%script%TAB%%TAB%{} static const char* npc_parse_function(char* w1, char* w2, char* w3, char* w4, const char* start, const char* buffer, const char* filepath) diff --git a/src/map/npc.h b/src/map/npc.h index 6f8737c65..f5dd9011c 100644 --- a/src/map/npc.h +++ b/src/map/npc.h @@ -172,4 +172,7 @@ int npc_cashshop_buylist(struct map_session_data *sd, int points, int count, uns int npc_rr_secure_timeout_timer(int tid, unsigned int tick, int id, intptr_t data); #endif +// @commands (script-based) +int npc_do_atcmd_event(struct map_session_data* sd, const char* command, const char* message, const char* eventname); + #endif /* _NPC_H_ */ diff --git a/src/map/script.c b/src/map/script.c index 0329f4928..6c34af6f2 100644 --- a/src/map/script.c +++ b/src/map/script.c @@ -4053,6 +4053,10 @@ int script_reload() { userfunc_db->clear(userfunc_db, db_script_free_code_sub); db_clear(scriptlabel_db); + // @commands (script based) + // Clear bindings + memset(atcmd_binding,0,sizeof(atcmd_binding)); + if(sleep_db) { struct linkdb_node *n = (struct linkdb_node *)sleep_db; while(n) { @@ -16347,6 +16351,100 @@ BUILDIN_FUNC(freeloop) { return 0; } + +/** + * @commands (script based) + **/ +BUILDIN_FUNC(bindatcmd) +{ + const char* atcmd; + const char* eventName; + int i = 0, level = 0, level2 = 0; + + atcmd = script_getstr(st,2); + eventName = script_getstr(st,3); + + if( script_hasdata(st,4) ) level = script_getnum(st,4); + if( script_hasdata(st,5) ) level2 = script_getnum(st,5); + + // check if event is already binded + ARR_FIND(0, MAX_ATCMD_BINDINGS, i, strcmp(atcmd_binding[i].command,atcmd) == 0); + if( i < MAX_ATCMD_BINDINGS ) + { + safestrncpy(atcmd_binding[i].npc_event, eventName, 50); + atcmd_binding[i].level = level; + atcmd_binding[i].level2 = level2; + } + else + { // make new binding + ARR_FIND(0, MAX_ATCMD_BINDINGS, i, atcmd_binding[i].command[0] == '\0'); + if( i < MAX_ATCMD_BINDINGS ) + { + safestrncpy(atcmd_binding[i].command, atcmd, 50); + safestrncpy(atcmd_binding[i].npc_event, eventName, 50); + atcmd_binding[i].level = level; + atcmd_binding[i].level2 = level2; + } + } + + return 0; +} + +BUILDIN_FUNC(unbindatcmd) +{ + const char* atcmd; + int i = 0; + + atcmd = script_getstr(st, 2); + + ARR_FIND(0, MAX_ATCMD_BINDINGS, i, strcmp(atcmd_binding[i].command, atcmd) == 0); + if( i < MAX_ATCMD_BINDINGS ) + memset(&atcmd_binding[i],0,sizeof(atcmd_binding[0])); + + return 0; +} + +BUILDIN_FUNC(useatcmd) +{ + TBL_PC dummy_sd; + TBL_PC* sd; + int fd; + const char* cmd; + + cmd = script_getstr(st,2); + + if( st->rid ) + { + sd = script_rid2sd(st); + fd = sd->fd; + } + else + { // Use a dummy character. + sd = &dummy_sd; + fd = 0; + + memset(&dummy_sd, 0, sizeof(TBL_PC)); + if( st->oid ) + { + struct block_list* bl = map_id2bl(st->oid); + memcpy(&dummy_sd.bl, bl, sizeof(struct block_list)); + if( bl->type == BL_NPC ) + safestrncpy(dummy_sd.status.name, ((TBL_NPC*)bl)->name, NAME_LENGTH); + } + } + + // compatibility with previous implementation (deprecated!) + if( cmd[0] != atcommand_symbol ) + { + cmd += strlen(sd->status.name); + while( *cmd != atcommand_symbol && *cmd != 0 ) + cmd++; + } + + is_atcommand(fd, sd, cmd, 1); + return 0; +} + // declarations that were supposed to be exported from npc_chat.c #ifdef PCRE_SUPPORT BUILDIN_FUNC(defpattern); @@ -16782,6 +16880,13 @@ struct script_function buildin_func[] = { BUILDIN_DEF(is_function,"s"), BUILDIN_DEF(get_revision,""), BUILDIN_DEF(freeloop,"i"), + /** + * @commands (script based) + **/ + BUILDIN_DEF(bindatcmd, "ss??"), + BUILDIN_DEF(unbindatcmd, "s"), + BUILDIN_DEF(useatcmd, "s"), + //Quest Log System [Inkfish] BUILDIN_DEF(setquest, "i"), BUILDIN_DEF(erasequest, "i"), diff --git a/src/map/script.h b/src/map/script.h index eed4c20c8..41c686660 100644 --- a/src/map/script.h +++ b/src/map/script.h @@ -187,4 +187,7 @@ int add_str(const char* p); const char* get_str(int id); int script_reload(void); +// @commands (script based) +void setd_sub(struct script_state *st, TBL_PC *sd, const char *varname, int elem, void *value, struct DBMap **ref); + #endif /* _SCRIPT_H_ */ -- cgit v1.2.3-60-g2f50