소스 검색

duplicate npc command script (#5960)

Co-authored-by: Aleos <aleos89@users.noreply.github.com>
Co-authored-by: Atemo <Atemo@users.noreply.github.com>
Co-authored-by: Lemongrass3110 <lemongrass@kstp.at>
Sader Fawall 2 년 전
부모
커밋
9c2576f47a
5개의 변경된 파일257개의 추가작업 그리고 5개의 파일을 삭제
  1. 77 3
      doc/sample/npc_test_duplicate.txt
  2. 12 0
      doc/script_commands.txt
  3. 54 0
      src/map/npc.cpp
  4. 1 0
      src/map/npc.hpp
  5. 113 2
      src/map/script.cpp

+ 77 - 3
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;
+}

+ 12 - 0
doc/script_commands.txt

@@ -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>}};
 

+ 54 - 0
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) {

+ 1 - 0
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);

+ 113 - 2
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 "<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>});
@@ -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,""),