Browse Source

OnTouch support when npc is walking (#5134)

* Added support to trigger 'OnTouch'/'OnTouch_' npc label when a npc is moving through npcwalkto script command.
* Fixed #502

Thanks to @aleos89, @wbgneto !
Atemo 5 years ago
parent
commit
0f9dde4e33
6 changed files with 143 additions and 108 deletions
  1. 1 1
      src/map/clif.cpp
  2. 1 1
      src/map/map.cpp
  3. 79 101
      src/map/npc.cpp
  4. 2 1
      src/map/npc.hpp
  5. 1 1
      src/map/status.cpp
  6. 59 3
      src/map/unit.cpp

+ 1 - 1
src/map/clif.cpp

@@ -10767,7 +10767,7 @@ void clif_parse_LoadEndAck(int fd,struct map_session_data *sd)
 
 	// For automatic triggering of NPCs after map loading (so you don't need to walk 1 step first)
 	if (map_getcell(sd->bl.m,sd->bl.x,sd->bl.y,CELL_CHKNPC))
-		npc_touch_areanpc(sd,sd->bl.m,sd->bl.x,sd->bl.y);
+		npc_touch_area_allnpc(sd,sd->bl.m,sd->bl.x,sd->bl.y);
 	else
 		sd->areanpc.clear();
 

+ 1 - 1
src/map/map.cpp

@@ -2663,7 +2663,7 @@ bool map_addnpc(int16 m,struct npc_data *nd)
 	}
 	// npcs with trigger area are grouped
 	// 0 < npc_num_warp < npc_num_area < npc_num
-	if (xs < 0 && ys < 0)
+	if (xs < 0 || ys < 0)
 		mapdata->npc[ mapdata->npc_num ] = nd;
 	else {
 		switch (nd->subtype) {

+ 79 - 101
src/map/npc.cpp

@@ -190,9 +190,7 @@ int npc_ontouch_event(struct map_session_data *sd, struct npc_data *nd)
 	// if( pc_ishiding(sd) )
 		// return 1; // Can't trigger 'OnTouch_'.
 
-	auto it = std::find(sd->npc_ontouch_.begin(), sd->npc_ontouch_.end(), nd->bl.id);
-
-	if (it != sd->npc_ontouch_.end())
+	if (util::vector_exists(sd->npc_ontouch_, nd->bl.id))
 		return 0;
 
 	safesnprintf(name, ARRAYLENGTH(name), "%s::%s", nd->exname, script_config.ontouch_event_name);
@@ -202,15 +200,16 @@ int npc_ontouch_event(struct map_session_data *sd, struct npc_data *nd)
 int npc_ontouch2_event(struct map_session_data *sd, struct npc_data *nd)
 {
 	char name[EVENT_NAME_LENGTH];
-	auto it = std::find(sd->areanpc.begin(), sd->areanpc.end(), nd->bl.id);
 
-	if (it != sd->areanpc.end())
+	if (util::vector_exists(sd->areanpc, nd->bl.id))
 		return 0;
 
 	safesnprintf(name, ARRAYLENGTH(name), "%s::%s", nd->exname, script_config.ontouch2_event_name);
 	return npc_event(sd,name,2);
 }
 
+int npc_touch_areanpc(struct map_session_data* sd, int16 m, int16 x, int16 y, struct npc_data *nd);
+
 /*==========================================
  * Sub-function of npc_enable, runs OnTouch event when enabled
  *------------------------------------------*/
@@ -223,35 +222,7 @@ int npc_enable_sub(struct block_list *bl, va_list ap)
 	if(bl->type == BL_PC)
 	{
 		TBL_PC *sd = (TBL_PC*)bl;
-
-		if (nd->sc.option&(OPTION_INVISIBLE|OPTION_CLOAK))
-			return 1;
-
-		switch (nd->subtype) {
-		case NPCTYPE_WARP:
-			if ((!nd->trigger_on_hidden && (pc_ishiding(sd) || (sd->sc.count && sd->sc.data[SC_CAMOUFLAGE]))) || pc_isdead(sd))
-				return 1;
-			if (!pc_job_can_entermap((enum e_job)sd->status.class_, map_mapindex2mapid(nd->u.warp.mapindex), sd->group_level))
-				return 1;
-			if (sd->count_rewarp > 10) {
-				ShowWarning("Prevented infinite warp loop for player (%d:%d). Please fix NPC: '%s', path: '%s'\n", sd->status.account_id, sd->status.char_id, nd->exname, nd->path);
-				sd->count_rewarp = 0;
-				return 1;
-			}
-			pc_setpos(sd, nd->u.warp.mapindex, nd->u.warp.x, nd->u.warp.y, CLR_OUTSIGHT);
-			break;
-		default:
-			// note : disablenpc doesn't reset the previous trigger status on official
-			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,1);
-				npc_click(sd,nd);
-			}
-			break;
-		}
+		npc_touch_areanpc(sd, bl->m, bl->x, bl->y, nd);
 	}
 	return 0;
 }
@@ -363,7 +334,7 @@ bool npc_enable_target(const char* name, uint32 char_id, int flag)
 			ys = nd->u.warp.ys;
 			break;
 		}
-		if (xs >= 0 || ys >= 0)
+		if (xs > -1 && ys > -1)
 			map_foreachinallarea( npc_enable_sub, nd->bl.m, nd->bl.x-xs, nd->bl.y-ys, nd->bl.x+xs, nd->bl.y+ys, BL_PC, nd );
 	}
 
@@ -1036,14 +1007,10 @@ int npc_event(struct map_session_data* sd, const char* eventname, int ontouch)
 
 		nd->touching_id = sd->bl.id;
 
-		auto it = std::find(sd->npc_ontouch_.begin(), sd->npc_ontouch_.end(), nd->bl.id);
-
-		if (it == sd->npc_ontouch_.end())
+		if (!util::vector_exists(sd->npc_ontouch_, nd->bl.id))
 			sd->npc_ontouch_.push_back(nd->bl.id);
 	} else if (ontouch == 2) { // OnTouch
-		auto it = std::find(sd->areanpc.begin(), sd->areanpc.end(), nd->bl.id);
-
-		if (it == sd->areanpc.end())
+		if (!util::vector_exists(sd->areanpc, nd->bl.id))
 			sd->areanpc.push_back(nd->bl.id);
 	}
 
@@ -1122,89 +1089,98 @@ int npc_touchnext_areanpc(struct map_session_data* sd, bool leavemap)
 	return found;
 }
 
+int npc_touch_areanpc(struct map_session_data* sd, int16 m, int16 x, int16 y, struct npc_data *nd)
+{
+	nullpo_retr(0, sd);
+	nullpo_retr(0, nd);
+
+	if (nd->sc.option&OPTION_INVISIBLE)
+		return 1; // a npc was found, but it is disabled
+	if (npc_is_cloaked(nd, sd))
+		return 1;
+
+	int xs = -1, ys = -1;
+	switch(nd->subtype) {
+	case NPCTYPE_WARP:
+		xs = nd->u.warp.xs;
+		ys = nd->u.warp.ys;
+		break;
+	case NPCTYPE_SCRIPT:
+		xs = nd->u.scr.xs;
+		ys = nd->u.scr.ys;
+		break;
+	default:
+		return 0;
+	}
+	if (xs < 0 || ys < 0)
+		return 0;
+	if (x < (nd->bl.x - xs) || x > (nd->bl.x + xs) || y < (nd->bl.y - ys) || y > (nd->bl.y + ys))
+		return 0;
+
+	switch (nd->subtype) {
+	case NPCTYPE_WARP:
+		if ((!nd->trigger_on_hidden && (pc_ishiding(sd) || (sd->sc.count && sd->sc.data[SC_CAMOUFLAGE]))) || pc_isdead(sd))
+			break; // hidden or dead chars cannot use warps
+		if (!pc_job_can_entermap((enum e_job)sd->status.class_, map_mapindex2mapid(nd->u.warp.mapindex), sd->group_level))
+			break;
+		if (sd->count_rewarp > 10) {
+			ShowWarning("Prevented infinite warp loop for player (%d:%d). Please fix NPC: '%s', path: '%s'\n", sd->status.account_id, sd->status.char_id, nd->exname, nd->path);
+			sd->count_rewarp = 0;
+			break;
+		}
+		pc_setpos(sd, nd->u.warp.mapindex, nd->u.warp.x, nd->u.warp.y, CLR_OUTSIGHT);
+		return 2;
+	case NPCTYPE_SCRIPT:
+		// warp type sorted first, no need to check if they override any other OnTouch areas.
+
+		if (npc_ontouch_event(sd, nd) > 0 && npc_ontouch2_event(sd, nd) > 0) { // failed to run OnTouch event, so just click the npc
+			if (!util::vector_exists(sd->areanpc, nd->bl.id))
+				sd->areanpc.push_back(nd->bl.id);
+
+			npc_click(sd, nd);
+		}
+		break;
+	}
+	return 1;
+}
+
 /*==========================================
  * 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 npc_touch_area_allnpc(struct map_session_data* sd, int16 m, int16 x, int16 y)
 {
-	int xs, ys, i, f = 1;
-
 	nullpo_retr(1, sd);
 
 	// Remove NPCs that are no longer within the OnTouch area
-	for (i = 0; i < sd->areanpc.size(); i++) {
+	for (size_t i = 0; i < sd->areanpc.size(); i++) {
 		struct npc_data *nd = map_id2nd(sd->areanpc[i]);
 
-		if (!nd || nd->subtype != NPCTYPE_SCRIPT ||
-			!(nd->bl.m == m && x >= nd->bl.x - nd->u.scr.xs && x <= nd->bl.x + nd->u.scr.xs && y >= nd->bl.y - nd->u.scr.ys && y <= nd->bl.y + nd->u.scr.ys))
-			sd->areanpc.erase(sd->areanpc.begin() + i);
+		if (!nd || nd->subtype != NPCTYPE_SCRIPT || !(nd->bl.m == m && x >= nd->bl.x - nd->u.scr.xs && x <= nd->bl.x + nd->u.scr.xs && y >= nd->bl.y - nd->u.scr.ys && y <= nd->bl.y + nd->u.scr.ys))
+			util::erase_at(sd->areanpc, i);
 	}
 
 	if (sd->state.block_action & PCBLOCK_NPCCLICK)
 		return 0;
 
 	struct map_data *mapdata = map_getmapdata(m);
+	int f = 1;
 
-	for (i = 0; i < mapdata->npc_num_area; i++) {
-		if (mapdata->npc[i]->sc.option&OPTION_INVISIBLE) {
-			f = 0; // a npc was found, but it is disabled; don't print warning
-			continue;
-		}
-
-		switch(mapdata->npc[i]->subtype) {
-		case NPCTYPE_WARP:
-			xs = mapdata->npc[i]->u.warp.xs;
-			ys = mapdata->npc[i]->u.warp.ys;
-			break;
-		case NPCTYPE_SCRIPT:
-			xs = mapdata->npc[i]->u.scr.xs;
-			ys = mapdata->npc[i]->u.scr.ys;
+	for (int i = 0; i < mapdata->npc_num_area; i++) {
+		switch( npc_touch_areanpc(sd, m, x, y, mapdata->npc[i]) ) {
+		case 0:
 			break;
-		default:
-			continue;
-		}
-
-		if (x >= mapdata->npc[i]->bl.x - xs && x <= mapdata->npc[i]->bl.x + xs && y >= mapdata->npc[i]->bl.y - ys && y <= mapdata->npc[i]->bl.y + ys) {
+		case 1:
 			f = 0;
-
-			if (npc_is_cloaked(mapdata->npc[i], sd))
-				continue;
-
-			switch (mapdata->npc[i]->subtype) {
-			case NPCTYPE_WARP:
-				if ((!mapdata->npc[i]->trigger_on_hidden && (pc_ishiding(sd) || (sd->sc.count && sd->sc.data[SC_CAMOUFLAGE]))) || pc_isdead(sd))
-					break; // hidden or dead chars cannot use warps
-				if (!pc_job_can_entermap((enum e_job)sd->status.class_, map_mapindex2mapid(mapdata->npc[i]->u.warp.mapindex), sd->group_level))
-					break;
-				if (sd->count_rewarp > 10) {
-					ShowWarning("Prevented infinite warp loop for player (%d:%d). Please fix NPC: '%s', path: '%s'\n", sd->status.account_id, sd->status.char_id, mapdata->npc[i]->exname, mapdata->npc[i]->path);
-					sd->count_rewarp = 0;
-					break;
-				}
-				pc_setpos(sd, mapdata->npc[i]->u.warp.mapindex, mapdata->npc[i]->u.warp.x, mapdata->npc[i]->u.warp.y, CLR_OUTSIGHT);
-				return 0;
-			case NPCTYPE_SCRIPT:
-				// warp type sorted first, no need to check if they override any other OnTouch areas.
-
-				if (npc_ontouch_event(sd, mapdata->npc[i]) > 0 && npc_ontouch2_event(sd, mapdata->npc[i]) > 0) { // failed to run OnTouch event, so just click the npc
-					auto it = std::find(sd->areanpc.begin(), sd->areanpc.end(), mapdata->npc[i]->bl.id);
-
-					if (it == sd->areanpc.end())
-						sd->areanpc.push_back(mapdata->npc[i]->bl.id);
-
-					npc_click(sd, mapdata->npc[i]);
-				}
-
-				break;
-			}
+			break;
+		case 2:
+			return 0;
 		}
 	}
-	
+
 	if (f == 1) {
-		ShowError("npc_touch_areanpc : stray NPC cell/NPC not found in the block on coordinates '%s',%d,%d\n", mapdata->name, x, y);
+		ShowError("npc_touch_area_allnpc : stray NPC cell/NPC not found in the block on coordinates '%s',%d,%d\n", mapdata->name, x, y);
 		return 1;
 	}
-
 	return 0;
 }
 
@@ -1238,6 +1214,8 @@ int npc_touch_areanpc2(struct mob_data *md)
 			default:
 				continue; // Keep Searching
 		}
+		if (xs < 0 || ys < 0)
+			continue;
 
 		if( x >= mapdata->npc[i]->bl.x-xs && x <= mapdata->npc[i]->bl.x+xs && y >= mapdata->npc[i]->bl.y-ys && y <= mapdata->npc[i]->bl.y+ys )
 		{ // In the npc touch area

+ 2 - 1
src/map/npc.hpp

@@ -1227,7 +1227,8 @@ struct view_data* npc_get_viewdata(int class_);
 int npc_chat_sub(struct block_list* bl, va_list ap);
 int npc_event_dequeue(struct map_session_data* sd,bool free_script_stack=true);
 int npc_event(struct map_session_data* sd, const char* eventname, int ontouch);
-int npc_touch_areanpc(struct map_session_data* sd, int16 m, int16 x, int16 y);
+int npc_touch_areanpc(struct map_session_data* sd, int16 m, int16 x, int16 y, struct npc_data* nd);
+int npc_touch_area_allnpc(struct map_session_data* sd, int16 m, int16 x, int16 y);
 int npc_touch_areanpc2(struct mob_data *md); // [Skotlex]
 int npc_check_areanpc(int flag, int16 m, int16 x, int16 y, int16 range);
 int npc_touchnext_areanpc(struct map_session_data* sd,bool leavemap);

+ 1 - 1
src/map/status.cpp

@@ -13801,7 +13801,7 @@ int status_change_end_(struct block_list* bl, enum sc_type type, int tid, const
 		skill_unit_move(bl,gettick(),1);
 
 	if(opt_flag&2 && sd && !sd->state.warping && map_getcell(bl->m,bl->x,bl->y,CELL_CHKNPC))
-		npc_touch_areanpc(sd,bl->m,bl->x,bl->y); // Trigger on-touch event.
+		npc_touch_area_allnpc(sd,bl->m,bl->x,bl->y); // Trigger on-touch event.
 
 	ers_free(sc_data_ers, sce);
 	return 1;

+ 59 - 3
src/map/unit.cpp

@@ -316,6 +316,42 @@ TIMER_FUNC(unit_step_timer){
 	return 1;
 }
 
+int unit_walktoxy_ontouch(struct block_list *bl, va_list ap)
+{
+	struct npc_data *nd;
+
+	nullpo_ret(bl);
+	nullpo_ret(nd = va_arg(ap,struct npc_data *));
+
+	switch( bl->type ) {
+	case BL_PC:
+		TBL_PC *sd = (TBL_PC*)bl;
+
+		if (sd == nullptr)
+			return 1;
+		if (sd->state.block_action & PCBLOCK_NPCCLICK)
+			return 1;
+
+		// Remove NPCs that are no longer within the OnTouch area
+		for (size_t i = 0; i < sd->areanpc.size(); i++) {
+			struct npc_data *nd = map_id2nd(sd->areanpc[i]);
+
+			if (!nd || nd->subtype != NPCTYPE_SCRIPT || !(nd->bl.m == bl->m && bl->x >= nd->bl.x - nd->u.scr.xs && bl->x <= nd->bl.x + nd->u.scr.xs && bl->y >= nd->bl.y - nd->u.scr.ys && bl->y <= nd->bl.y + nd->u.scr.ys))
+				rathena::util::erase_at(sd->areanpc, i);
+		}
+		npc_touchnext_areanpc(sd, false);
+
+		if (map_getcell(bl->m, bl->x, bl->y, CELL_CHKNPC) == 0)
+			return 1;
+
+		npc_touch_areanpc(sd, bl->m, bl->x, bl->y, nd);
+		break;
+	// case BL_MOB:	// unsupported
+	}
+	
+	return 0;
+}
+
 /**
  * Defines when to refresh the walking character to object and restart the timer if applicable
  * Also checks for speed update, target location, and slave teleport timers
@@ -332,6 +368,7 @@ static TIMER_FUNC(unit_walktoxy_timer){
 	struct unit_data *ud;
 	TBL_PC *sd=NULL;
 	TBL_MOB *md=NULL;
+	TBL_NPC *nd=NULL;
 
 	bl = map_id2bl(id);
 
@@ -341,6 +378,7 @@ static TIMER_FUNC(unit_walktoxy_timer){
 	switch(bl->type) { // svoid useless cast, we can only be 1 type
 		case BL_PC: sd = BL_CAST(BL_PC, bl); break;
 		case BL_MOB: md = BL_CAST(BL_MOB, bl); break;
+		case BL_NPC: nd = BL_CAST(BL_NPC, bl); break;
 	}
 
 	ud = unit_bl2ud(bl);
@@ -460,7 +498,7 @@ static TIMER_FUNC(unit_walktoxy_timer){
 			if( !sd->npc_ontouch_.empty() )
 				npc_touchnext_areanpc(sd,false);
 			if(map_getcell(bl->m,x,y,CELL_CHKNPC)) {
-				npc_touch_areanpc(sd,bl->m,x,y);
+				npc_touch_area_allnpc(sd,bl->m,x,y);
 				if (bl->prev == NULL) // Script could have warped char, abort remaining of the function.
 					return 0;
 			} else
@@ -493,6 +531,24 @@ static TIMER_FUNC(unit_walktoxy_timer){
 				clif_move(ud);
 			}
 			break;
+		case BL_NPC:
+			if (nd->sc.option&OPTION_INVISIBLE)
+				break;
+
+			int xs = -1, ys = -1;
+			switch (nd->subtype) {
+			case NPCTYPE_SCRIPT:
+				xs = nd->u.scr.xs;
+				ys = nd->u.scr.ys;
+				break;
+			case NPCTYPE_WARP:
+				xs = nd->u.warp.xs;
+				ys = nd->u.warp.ys;
+				break;
+			}
+			if (xs > -1 && ys > -1)
+				map_foreachinmap(unit_walktoxy_ontouch, nd->bl.m, BL_PC, nd);
+			break;
 	}
 
 	if(tid == INVALID_TIMER) // A directly invoked timer is from battle_stop_walking, therefore the rest is irrelevant.
@@ -988,7 +1044,7 @@ bool unit_movepos(struct block_list *bl, short dst_x, short dst_y, int easy, boo
 			npc_touchnext_areanpc(sd,false);
 
 		if(map_getcell(bl->m,bl->x,bl->y,CELL_CHKNPC)) {
-			npc_touch_areanpc(sd, bl->m, bl->x, bl->y);
+			npc_touch_area_allnpc(sd, bl->m, bl->x, bl->y);
 
 			if (bl->prev == NULL) // Script could have warped char, abort remaining of the function.
 				return false;
@@ -1119,7 +1175,7 @@ int unit_blown(struct block_list* bl, int dx, int dy, int count, enum e_skill_bl
 					npc_touchnext_areanpc(sd, false);
 
 				if(map_getcell(bl->m, bl->x, bl->y, CELL_CHKNPC))
-					npc_touch_areanpc(sd, bl->m, bl->x, bl->y);
+					npc_touch_area_allnpc(sd, bl->m, bl->x, bl->y);
 				else
 					sd->areanpc.clear();
 			}