Skip to content

Commit

Permalink
duplicate npc command script (rathena#5960)
Browse files Browse the repository at this point in the history
Co-authored-by: Aleos <[email protected]>
Co-authored-by: Atemo <[email protected]>
Co-authored-by: Lemongrass3110 <[email protected]>
  • Loading branch information
4 people authored Oct 1, 2022
1 parent c7ec076 commit 9c2576f
Show file tree
Hide file tree
Showing 5 changed files with 257 additions and 5 deletions.
80 changes: 77 additions & 3 deletions doc/sample/npc_test_duplicate.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;
}
12 changes: 12 additions & 0 deletions doc/script_commands.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6840,6 +6840,18 @@ This command will fully unload a NPC object and all of it's duplicates.

---------------------------------------

*duplicate "<NPC name>","<map>",<x>,<y>{,"<Duplicate NPC name>"{,<sprite>{,<dir>{,<xs>{,<xy>}}}}};

This command will duplicate the NPC with the given <NPC name> on <map> at <x>/<y>.
If <Duplicate NPC name>, <sprite>, <dir>, <xs> or <ys> 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 {"<NPC object name>"{,<character ID>}};
*cloakoffnpc {"<NPC object name>"{,<character ID>}};

Expand Down
54 changes: 54 additions & 0 deletions src/map/npc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions src/map/npc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
115 changes: 113 additions & 2 deletions src/map/script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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;
}
}

Expand Down Expand Up @@ -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 "<NPC name>","<map>",<x>,<y>{,"<Duplicate NPC name>"{,<sprite>{,<dir>{,<xs>{,<xy>}}}}};
*/
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(<achievement ID>{,<char ID>});
Expand Down Expand Up @@ -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,""),
Expand Down

0 comments on commit 9c2576f

Please sign in to comment.