From 0f9dde4e33db5947958f304d56c623ba902d2ba6 Mon Sep 17 00:00:00 2001 From: Atemo Date: Fri, 26 Jun 2020 15:59:39 +0200 Subject: [PATCH] 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 ! --- src/map/clif.cpp | 2 +- src/map/map.cpp | 2 +- src/map/npc.cpp | 180 ++++++++++++++++++++------------------------- src/map/npc.hpp | 3 +- src/map/status.cpp | 2 +- src/map/unit.cpp | 62 +++++++++++++++- 6 files changed, 143 insertions(+), 108 deletions(-) diff --git a/src/map/clif.cpp b/src/map/clif.cpp index 17416d8f0c..f968926c22 100644 --- a/src/map/clif.cpp +++ b/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(); diff --git a/src/map/map.cpp b/src/map/map.cpp index 2dfcb78847..075f290795 100644 --- a/src/map/map.cpp +++ b/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) { diff --git a/src/map/npc.cpp b/src/map/npc.cpp index 668685a865..bf981c06fa 100644 --- a/src/map/npc.cpp +++ b/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; + for (int i = 0; i < mapdata->npc_num_area; i++) { + switch( npc_touch_areanpc(sd, m, x, y, mapdata->npc[i]) ) { + case 0: break; - case NPCTYPE_SCRIPT: - xs = mapdata->npc[i]->u.scr.xs; - ys = mapdata->npc[i]->u.scr.ys; - 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 diff --git a/src/map/npc.hpp b/src/map/npc.hpp index 42ca453d09..61e0c567cd 100644 --- a/src/map/npc.hpp +++ b/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); diff --git a/src/map/status.cpp b/src/map/status.cpp index 882d701be1..fbdbc3bab1 100644 --- a/src/map/status.cpp +++ b/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; diff --git a/src/map/unit.cpp b/src/map/unit.cpp index dcae524a65..960c271590 100644 --- a/src/map/unit.cpp +++ b/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(); }