diff --git a/doc/sample/npc_test_duplicate.txt b/doc/sample/npc_test_duplicate.txt index 2ca9694995..cb448cd24a 100644 --- a/doc/sample/npc_test_duplicate.txt +++ b/doc/sample/npc_test_duplicate.txt @@ -3,7 +3,7 @@ //===== By: ================================================== //= rAthena Dev Team //===== Last Updated: ======================================== -//= 20180831 +//= 20211112 //===== Description: ========================================= //= An example of how duplicate NPCs are handled: //= NPC variables are shared between all duplicates. @@ -18,14 +18,88 @@ prontera,150,175,4 script Duplicate Test Script 909,{ close; OnInit: - getmapxy(.map$, .x, .y, 1); + getmapxy(.map$, .x, .y, BL_NPC); end; OnTouch: - getmapxy(.map$, .x, .y, 1); + getmapxy(.map$, .x, .y, BL_NPC); emotion ET_SCISSOR; end; } prontera,155,175,4 duplicate(Duplicate Test Script) Duplicate Test2 909,2,2 prontera,160,175,4 duplicate(Duplicate Test Script) Duplicate Test3 909,3,3 + +//duplicate command script +prontera,150,168,4 script Duplicate Command Test 909,{ + mes "Would you like to create a new NPC?"; + .@original_npc$ = "original_npc_unique_name"; + + mes "Input Map Name"; + mes "recommended 'prontera'"; + input .@map$; + clear; + mes "Input x"; + mes "recommended '155'"; + input .@x,0; + clear; + mes "Input y"; + mes "recommended '168'"; + input .@y,0; + clear; + + switch( .@s = select( "Provide no info:With name:With name and look:With name, look and dir:cancel" ) ){ + case 1: + .@new_npc$ = duplicate( .@original_npc$, .@map$, .@x, .@y ); + break; + case 2: + mes "Input Duplicate NPC Name"; + input .@name$; + clear; + .@new_npc$ = duplicate( .@original_npc$, .@map$, .@x, .@y, .@name$ ); + break; + case 3: + mes "Input Duplicate NPC Name"; + input .@name$; + clear; + mes "Input look"; + mes "recommended '445'"; + input .@look,0; + .@new_npc$ = duplicate( .@original_npc$, .@map$, .@x, .@y, .@name$, .@look ); + break; + case 4: + mes "Input Duplicate NPC Name"; + input .@name$; + clear; + mes "Input look"; + mes "recommended '445'"; + input .@look,0; + clear; + mes "Input dir"; + mes "between " + DIR_NORTH + " and " + DIR_NORTHEAST; + input .@dir,DIR_NORTH,DIR_NORTHEAST; + .@new_npc$ = duplicate( .@original_npc$, .@map$, .@x, .@y, .@name$, .@look, .@dir ); + break; + default: + mes "Ok, see you next time!"; + close; + } + + if( getnpcid( 0, .@new_npc$ ) == 0 ){ + mes "Something went wrong!"; + mes "The new NPC could not be found!"; + close; + } + + clear; + mes "The new NPC is now at " + .@map$ + "," + .@x + "," + .@y; + end; +} + +prontera,150,165,0 script test npc::original_npc_unique_name 444,{ + getmapxy(.@map$, .@x, .@y, BL_NPC); + mes "Hi."; + mes "My Unique Name is: " + strnpcinfo(3); + mes "My coords are "+ .@map$ +", "+ .@x +"/" +.@y ; + close; +} \ No newline at end of file diff --git a/doc/script_commands.txt b/doc/script_commands.txt index fa090c58ba..6786c431bb 100644 --- a/doc/script_commands.txt +++ b/doc/script_commands.txt @@ -6840,6 +6840,18 @@ This command will fully unload a NPC object and all of it's duplicates. --------------------------------------- +*duplicate "","",,{,""{,{,{,{,}}}}}; + +This command will duplicate the NPC with the given on at /. +If , , , or is not provided the value of the original NPC will be used. +The Unique name of the new duplicated NPC is returned on success. An empty string is returned on failure. + +NOTE: + Duplicates will always have the same NPC variables as the original NPC. + Editing a NPC variable in a duplicate or the original NPC will change it for the others. + +--------------------------------------- + *cloakonnpc {""{,}}; *cloakoffnpc {""{,}}; diff --git a/src/map/npc.cpp b/src/map/npc.cpp index 1f61feb8a1..bddd9eb9d2 100644 --- a/src/map/npc.cpp +++ b/src/map/npc.cpp @@ -5630,6 +5630,60 @@ int npc_script_event(struct map_session_data* sd, enum npce_event type){ return vector.size(); } +/** + * Duplicates a NPC. + * nd: Original NPC data + * name: Duplicate NPC name + * m: Map ID of duplicate NPC + * x: X coordinate of duplicate NPC + * y: Y coordinate of duplicate NPC + * class_: View of duplicate NPC + * dir: Facing direction of duplicate NPC + * Returns duplicate NPC data on success + */ +npc_data* npc_duplicate_npc( npc_data* nd, char name[NPC_NAME_LENGTH + 1], int16 mapid, int16 x, int16 y, int class_, uint8 dir, int16 xs, int16 ys ){ + static char w1[128], w2[128], w3[128], w4[128]; + const char* stat_buf = "- call from duplicate subsystem -\n"; + char exname[NPC_NAME_LENGTH + 1]; + + snprintf(w1, sizeof(w1), "%s,%d,%d,%d", map_getmapdata(mapid)->name, x, y, dir); + snprintf(w2, sizeof(w2), "duplicate(%s)", nd->exname); + + //Making sure the generated name is not used for another npc. + int i = 0; + snprintf(exname, ARRAYLENGTH(exname), "%d_%d_%d_%d", i, mapid, x, y); + while (npc_name2id(exname) != nullptr) { + ++i; + snprintf(exname, ARRAYLENGTH(exname), "%d_%d_%d_%d", i, mapid, x, y); + } + + snprintf(w3, sizeof(w3), "%s::%s", name, exname); + + if( xs >= 0 && ys >= 0 ){ + snprintf( w4, sizeof( w4 ), "%d,%d,%d", class_, xs, ys ); // Touch Area + }else{ + snprintf( w4, sizeof( w4 ), "%d", class_ ); + } + + npc_parse_duplicate(w1, w2, w3, w4, stat_buf, stat_buf, "DUPLICATE");//DUPLICATE means nothing for now. + + npc_data* dnd = npc_name2id( exname ); + + // No need to try and execute any events + if( dnd == nullptr ){ + return nullptr; + } + + //run OnInit Events + char evname[EVENT_NAME_LENGTH]; + safesnprintf(evname, EVENT_NAME_LENGTH, "%s::%s", exname, script_config.init_event_name); + if ((struct event_data*)strdb_get(ev_db, evname)) { + npc_event_do(evname); + } + + return dnd; +} + const char *npc_get_script_event_name(int npce_index) { switch (npce_index) { diff --git a/src/map/npc.hpp b/src/map/npc.hpp index 47730bd2b8..3c23228307 100644 --- a/src/map/npc.hpp +++ b/src/map/npc.hpp @@ -1494,6 +1494,7 @@ void npc_parse_mob2(struct spawn_data* mob); 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 npc_globalmessage(const char* name,const char* mes); const char *npc_get_script_event_name(int npce_index); +npc_data* npc_duplicate_npc( npc_data* nd, char name[NPC_NAME_LENGTH + 1], int16 mapid, int16 x, int16 y, int class_, uint8 dir, int16 xs, int16 ys ); void npc_setcells(struct npc_data* nd); void npc_unsetcells(struct npc_data* nd); diff --git a/src/map/script.cpp b/src/map/script.cpp index 365e7284f5..6af0ee0d5e 100644 --- a/src/map/script.cpp +++ b/src/map/script.cpp @@ -8815,6 +8815,7 @@ BUILDIN_FUNC(getcharid) /*========================================== * returns the GID of an NPC + * Returns 0 if the NPC name provided is not found. *------------------------------------------*/ BUILDIN_FUNC(getnpcid) { @@ -8825,9 +8826,9 @@ BUILDIN_FUNC(getnpcid) {// unique npc name if( ( nd = npc_name2id(script_getstr(st,3)) ) == NULL ) { - ShowError("buildin_getnpcid: No such NPC '%s'.\n", script_getstr(st,3)); + //Npc not found. script_pushint(st,0); - return SCRIPT_CMD_FAILURE; + return SCRIPT_CMD_SUCCESS; } } @@ -25020,6 +25021,115 @@ BUILDIN_FUNC(unloadnpc) { return SCRIPT_CMD_SUCCESS; } +/** + * Duplicate a NPC. + * Return the duplicate Unique name on success or empty string on failure. + * duplicate "","",,{,""{,{,{,{,}}}}}; + */ +BUILDIN_FUNC(duplicate) +{ + const char* old_npcname = script_getstr( st, 2 ); + npc_data* nd = npc_name2id( old_npcname ); + + if( nd == nullptr ){ + ShowError( "buildin_duplicate: No such NPC '%s'.\n", old_npcname ); + script_pushstrcopy( st, "" ); + return SCRIPT_CMD_FAILURE; + } + + const char* mapname = script_getstr( st, 3 ); + int16 mapid = map_mapname2mapid( mapname ); + + if( mapid < 0 ){ + ShowError( "buildin_duplicate: map '%s' in not found!\n", mapname ); + script_pushstrcopy( st, "" ); + return SCRIPT_CMD_FAILURE; + } + + struct map_data* mapdata = map_getmapdata( mapid ); + + if( mapdata == nullptr ){ + // Should not happen, but who knows... + ShowError( "buildin_duplicate: mapdata for '%s' is unavailable!\n", mapname ); + script_pushstrcopy( st, "" ); + return SCRIPT_CMD_FAILURE; + } + + int16 x = script_getnum( st, 4 ); + + if( x < 0 || x >= mapdata->xs ){ + ShowError( "buildin_duplicate: x coordinate %hd is out of bounds for map %s[0-%hd]!\n", x, mapname, mapdata->xs ); + script_pushstrcopy( st, "" ); + return SCRIPT_CMD_FAILURE; + } + + int16 y = script_getnum( st, 5 ); + + if( y < 0 || y >= mapdata->ys ){ + ShowError( "buildin_duplicate: y coordinate %hd is out of bounds for map %s[0-%hd]!\n", y, mapname, mapdata->ys ); + script_pushstrcopy( st, "" ); + return SCRIPT_CMD_FAILURE; + } + + char name[NPC_NAME_LENGTH + 1]; + + if( script_hasdata( st, 6 ) ){ + const char* new_name = script_getstr( st, 6 ); + + if( strlen( new_name ) > NPC_NAME_LENGTH ){ + ShowError( "buildin_duplicate: new NPC name \"%s\" is too long!\n", new_name ); + script_pushstrcopy( st, "" ); + return SCRIPT_CMD_FAILURE; + } + + safestrncpy( name, new_name, sizeof( name ) ); + }else{ + safestrncpy( name, nd->name, sizeof( name ) ); + } + + int class_; + + if( script_hasdata( st, 7 ) ){ + class_ = script_getnum( st, 7 ); + }else{ + class_ = nd->class_; + } + + uint8 dir; + + if( script_hasdata( st, 8 ) ){ + dir = script_getnum( st, 8 ); + }else{ + dir = nd->ud.dir; + } + + int16 xs; + + if( script_hasdata( st, 9 ) ){ + xs = script_getnum( st, 9 ); + }else{ + xs = nd->u.scr.xs; + } + + int16 ys; + + if( script_hasdata( st, 10 ) ){ + ys = script_getnum( st, 10 ); + }else{ + ys = nd->u.scr.ys; + } + + npc_data* dnd = npc_duplicate_npc( nd, name, mapid, x, y, class_, dir, xs, ys ); + + if( dnd == nullptr ){ + script_pushstrcopy( st, "" ); + return SCRIPT_CMD_FAILURE; + }else{ + script_pushstrcopy( st, dnd->exname ); + return SCRIPT_CMD_SUCCESS; + } +} + /** * Add an achievement to the player's log * achievementadd({,}); @@ -27173,6 +27283,7 @@ struct script_function buildin_func[] = { BUILDIN_DEF(jobcanentermap,"s?"), BUILDIN_DEF(openstorage2,"ii?"), BUILDIN_DEF(unloadnpc, "s"), + BUILDIN_DEF(duplicate, "ssii?????"), // WoE TE BUILDIN_DEF(agitstart3,""),