[Suggestion] Implement cloakonnpc/cloakoffnpc (#4688)

* The player can interact with a NPC cloaked by cloakonnpc command (click, mob event..) but the NPC trigger area is disabled (= the OnTouch* part is disabled).
* The changes last until the player leaves the map, logs out, or the npc option is updated by disablenpc/enablenpc/hideonnpc/hideoffnpc/cloakonnpc/cloakoffnpc commands.

Thanks to @aleos89
This commit is contained in:
Atemo 2020-03-11 22:47:23 +01:00 committed by GitHub
parent e2de896414
commit de80c5aab6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 228 additions and 72 deletions

View File

@ -6493,6 +6493,28 @@ while hidden then revealing.... you can wonder around =P
This command will fully unload a NPC object and all of it's duplicates.
---------------------------------------
*cloakonnpc "<NPC object name>"{,<character ID>};
*cloakoffnpc "<NPC object name>"{,<character ID>};
These commands will make the NPC object specified display as cloaked/uncloaked,
even though not actually disabled.
The player can interact with a NPC cloaked (via NPC click, monster event..)
but the NPC trigger area is disabled.
If <character ID> is given then the NPC will only display to the specified
player until he/she leaves the map, logs out, or the npc option is changed.
If no <character ID> is specified it will display to the area.
---------------------------------------
*isnpccloaked "<NPC object name>"{,<character ID>};
Returns true if the NPC has been cloaked to the attached player or given
<character ID>, false otherwise. This works in association with cloakonnpc
when it is targetting a specific character.
---------------------------------------
*doevent "<NPC object name>::<event label>";

View File

@ -980,7 +980,7 @@ static int clif_setlevel(struct block_list* bl) {
/*==========================================
* Prepares 'unit standing/spawning' packet
*------------------------------------------*/
static int clif_set_unit_idle(struct block_list* bl, unsigned char* buffer, bool spawn)
static int clif_set_unit_idle(struct block_list* bl, unsigned char* buffer, bool spawn, bool option, unsigned int option_val)
{
struct map_session_data* sd;
struct status_change* sc = status_get_sc(bl);
@ -996,6 +996,9 @@ static int clif_set_unit_idle(struct block_list* bl, unsigned char* buffer, bool
#endif
sd = BL_CAST(BL_PC, bl);
if (!option)
option_val = ((sc) ? sc->option : 0);
#if PACKETVER < 20091103
if(type)
WBUFW(buf,0) = spawn ? 0x7c : 0x78;
@ -1053,7 +1056,7 @@ static int clif_set_unit_idle(struct block_list* bl, unsigned char* buffer, bool
WBUFW(buf,10) = (sc)? sc->opt2 : 0;
#if PACKETVER < 20091103
if (type&&spawn) { //uses an older and different packet structure
WBUFW(buf,12) = (sc)? sc->option : 0;
WBUFW(buf,12) = option_val;
WBUFW(buf,14) = vd->hair_style;
WBUFW(buf,16) = vd->weapon;
WBUFW(buf,18) = vd->head_bottom;
@ -1062,18 +1065,18 @@ static int clif_set_unit_idle(struct block_list* bl, unsigned char* buffer, bool
} else {
#endif
#if PACKETVER >= 20091103
WBUFL(buf,12) = (sc)? sc->option : 0;
WBUFL(buf,12) = option_val;
offset+=2;
buf = WBUFP(buffer,offset);
#elif PACKETVER >= 7
if (!type) {
WBUFL(buf,12) = (sc)? sc->option : 0;
WBUFL(buf,12) = option_val;
offset+=2;
buf = WBUFP(buffer,offset);
} else
WBUFW(buf,12) = (sc)? sc->option : 0;
WBUFW(buf,12) = option_val;
#else
WBUFW(buf,12) = (sc)? sc->option : 0;
WBUFW(buf,12) = option_val;
#endif
WBUFW(buf,14) = vd->class_;
WBUFW(buf,16) = vd->hair_style;
@ -1455,7 +1458,7 @@ int clif_spawn(struct block_list *bl)
if(bl->type == BL_NPC && !((TBL_NPC*)bl)->chat_id && (((TBL_NPC*)bl)->sc.option&OPTION_INVISIBLE))
return 0;
len = clif_set_unit_idle(bl, buf, (bl->type == BL_NPC && vd->dead_sit ? false : true));
len = clif_set_unit_idle(bl, buf, (bl->type == BL_NPC && vd->dead_sit ? false : true), false, 0);
clif_send(buf, len, bl, AREA_WOS);
if (disguised(bl))
clif_setdisguise(bl, buf, len);
@ -3937,57 +3940,60 @@ void clif_misceffect(struct block_list* bl,int type)
/// Notifies clients in the area of a state change.
/// 0119 <id>.L <body state>.W <health state>.W <effect state>.W <pk mode>.B (ZC_STATE_CHANGE)
/// 0229 <id>.L <body state>.W <health state>.W <effect state>.L <pk mode>.B (ZC_STATE_CHANGE3)
void clif_changeoption(struct block_list* bl)
void clif_changeoption_target(struct block_list* bl, struct block_list *target)
{
unsigned char buf[32];
struct status_change *sc;
struct map_session_data* sd;
nullpo_retv(bl);
sc = status_get_sc(bl);
if (!sc) return; //How can an option change if there's no sc?
sd = BL_CAST(BL_PC, bl);
#if PACKETVER >= 7
WBUFW(buf,0) = 0x229;
struct status_change *sc = status_get_sc(bl);
if (!sc || (target && (target->type != BL_PC || bl->type != BL_NPC)))
return; //How can an option change if there's no sc?
struct map_session_data *sd = BL_CAST(BL_PC, bl);
unsigned char buf[32];
#if PAKCETVER >= 7
int cmd = 0x229;
#else
int cmd = 0x119;
#endif
WBUFW(buf,0) = cmd;
WBUFL(buf,2) = bl->id;
WBUFW(buf,6) = sc->opt1;
WBUFW(buf,8) = sc->opt2;
WBUFL(buf,10) = sc->option;
#if PACKETVER >= 7
WBUFB(buf,14) = (sd)? sd->status.karma : 0;
if(disguised(bl)) {
clif_send(buf,packet_len(0x229),bl,AREA_WOS);
WBUFL(buf,2) = -bl->id;
clif_send(buf,packet_len(0x229),bl,SELF);
WBUFL(buf,2) = bl->id;
WBUFL(buf,10) = OPTION_INVISIBLE;
clif_send(buf,packet_len(0x229),bl,SELF);
} else
clif_send(buf,packet_len(0x229),bl,AREA);
#else
WBUFW(buf,0) = 0x119;
WBUFL(buf,2) = bl->id;
WBUFW(buf,6) = sc->opt1;
WBUFW(buf,8) = sc->opt2;
WBUFW(buf,10) = sc->option;
WBUFB(buf,12) = (sd)? sd->status.karma : 0;
if(disguised(bl)) {
clif_send(buf,packet_len(0x119),bl,AREA_WOS);
WBUFL(buf,2) = -bl->id;
clif_send(buf,packet_len(0x119),bl,SELF);
WBUFL(buf,2) = bl->id;
WBUFW(buf,10) = OPTION_INVISIBLE;
clif_send(buf,packet_len(0x119),bl,SELF);
} else
clif_send(buf,packet_len(0x119),bl,AREA);
#endif
if (!target) {
if (disguised(bl)) {
clif_send(buf,packet_len(cmd),bl,AREA_WOS);
WBUFL(buf,2) = -bl->id;
clif_send(buf,packet_len(cmd),bl,SELF);
WBUFL(buf,2) = bl->id;
WBUFL(buf,10) = OPTION_INVISIBLE;
clif_send(buf,packet_len(cmd),bl,SELF);
} else
clif_send(buf,packet_len(cmd),bl,AREA);
//Whenever we send "changeoption" to the client, the provoke icon is lost
//There is probably an option for the provoke icon, but as we don't know it, we have to do this for now
if (sc->data[SC_PROVOKE]) {
const struct TimerData *td = get_timer(sc->data[SC_PROVOKE]->timer);
//Whenever we send "changeoption" to the client, the provoke icon is lost
//There is probably an option for the provoke icon, but as we don't know it, we have to do this for now
if (sc->data[SC_PROVOKE]) {
const struct TimerData *td = get_timer(sc->data[SC_PROVOKE]->timer);
clif_status_change(bl, StatusIconChangeTable[SC_PROVOKE], 1, (!td ? INFINITE_TICK : DIFF_TICK(td->tick, gettick())), 0, 0, 0);
clif_status_change(bl, StatusIconChangeTable[SC_PROVOKE], 1, (!td ? INFINITE_TICK : DIFF_TICK(td->tick, gettick())), 0, 0, 0);
}
}
else {
if (disguised(bl)) {
WBUFL(buf,2) = -bl->id;
clif_send(buf,packet_len(cmd),target,SELF);
WBUFL(buf,2) = bl->id;
WBUFL(buf,10) = OPTION_INVISIBLE;
}
clif_send(buf,packet_len(cmd),target,SELF);
}
}
@ -4644,10 +4650,14 @@ static void clif_getareachar_pc(struct map_session_data* sd,struct map_session_d
void clif_getareachar_unit(struct map_session_data* sd,struct block_list *bl)
{
nullpo_retv(bl);
uint8 buf[128];
struct unit_data *ud;
struct view_data *vd;
int len;
bool option = false;
unsigned int option_val = 0;
vd = status_get_viewdata(bl);
if (!vd || vd->class_ == JT_INVISIBLE)
@ -4660,7 +4670,15 @@ void clif_getareachar_unit(struct map_session_data* sd,struct block_list *bl)
return;
ud = unit_bl2ud(bl);
len = ( ud && ud->walktimer != INVALID_TIMER ) ? clif_set_unit_walking(bl,ud,buf) : clif_set_unit_idle(bl,buf,false);
if (sd && bl->type == BL_NPC) { // npc option changed?
npc_data* nd = BL_CAST(BL_NPC, bl);
option_val = nd->sc.option;
option = true;
if (std::find(sd->cloaked_npc.begin(), sd->cloaked_npc.end(), nd->bl.id) != sd->cloaked_npc.end())
option_val ^= OPTION_CLOAK;
}
len = ( ud && ud->walktimer != INVALID_TIMER ) ? clif_set_unit_walking(bl,ud,buf) : clif_set_unit_idle(bl,buf,false,option,option_val);
clif_send(buf,len,&sd->bl,SELF);
if (vd->cloth_color)

View File

@ -631,7 +631,8 @@ void clif_statusupack(struct map_session_data *sd,int type,int ok,int val); // s
void clif_equipitemack(struct map_session_data *sd,int n,int pos,uint8 flag); // self
void clif_unequipitemack(struct map_session_data *sd,int n,int pos,int ok); // self
void clif_misceffect(struct block_list* bl,int type); // area
void clif_changeoption(struct block_list* bl); // area
void clif_changeoption_target(struct block_list* bl, struct block_list* target);
#define clif_changeoption(bl) clif_changeoption_target(bl, NULL) // area
void clif_changeoption2(struct block_list* bl); // area
void clif_useitemack(struct map_session_data *sd,int index,int amount,bool ok); // self
void clif_GlobalMessage(struct block_list* bl, const char* message,enum send_target target);

View File

@ -252,41 +252,102 @@ int npc_enable_sub(struct block_list *bl, va_list ap)
return 0;
}
bool npc_is_cloaked(struct npc_data* nd, struct map_session_data* sd) {
bool npc_cloaked = (nd->sc.option & OPTION_CLOAK) ? true : false;
if (std::find(sd->cloaked_npc.begin(), sd->cloaked_npc.end(), nd->bl.id) != sd->cloaked_npc.end())
return (!npc_cloaked);
return npc_cloaked;
}
static int npc_cloaked_sub(struct block_list *bl, va_list ap)
{
struct map_session_data* sd;
nullpo_ret(bl);
nullpo_ret(sd = (struct map_session_data *)bl);
int id = va_arg(ap, int);
auto it = std::find(sd->cloaked_npc.begin(), sd->cloaked_npc.end(), id);
if (it != sd->cloaked_npc.end())
sd->cloaked_npc.erase(it);
return 1;
}
/*==========================================
* Disable / Enable NPC
*------------------------------------------*/
bool npc_enable(const char* name, int flag)
bool npc_enable_target(const char* name, uint32 char_id, int flag)
{
struct npc_data* nd = npc_name2id(name);
if (nd==NULL)
{
ShowError("npc_enable: Attempted to %s a non-existing NPC '%s' (flag=%d).\n", (flag&3) ? "show" : "hide", name, flag);
if (!nd) {
ShowError("npc_enable: Attempted to %s a non-existing NPC '%s' (flag=%d).\n", (flag&11) ? "show" : "hide", name, flag);
return false;
}
if (flag&1) {
nd->sc.option&=~OPTION_INVISIBLE;
clif_spawn(&nd->bl);
} else if (flag&2)
nd->sc.option&=~OPTION_HIDE;
else if (flag&4)
nd->sc.option|= OPTION_HIDE;
else { //Can't change the view_data to invisible class because the view_data for all npcs is shared! [Skotlex]
nd->sc.option|= OPTION_INVISIBLE;
clif_clearunit_area(&nd->bl,CLR_OUTSIGHT); // Hack to trick maya purple card [Xazax]
if (char_id > 0 && (flag & 24)) {
map_session_data *sd = map_charid2sd(char_id);
if (!sd) {
ShowError("npc_enable: Attempted to %s a NPC '%s' on an invalid target %d.\n", (flag & 8) ? "show" : "hide", name, char_id);
return false;
}
unsigned int option = nd->sc.option;
if (flag&8)
nd->sc.option &= ~OPTION_CLOAK;
else
nd->sc.option |= OPTION_CLOAK;
auto it = std::find(sd->cloaked_npc.begin(), sd->cloaked_npc.end(), nd->bl.id);
if (it == sd->cloaked_npc.end() && option != nd->sc.option)
sd->cloaked_npc.push_back(nd->bl.id);
else if (it != sd->cloaked_npc.end() && option == nd->sc.option)
sd->cloaked_npc.erase(it);
if (nd->class_ != JT_WARPNPC && nd->class_ != JT_GUILD_FLAG)
clif_changeoption_target(&nd->bl, &sd->bl);
else {
if (nd->sc.option&(OPTION_HIDE|OPTION_INVISIBLE|OPTION_CLOAK))
clif_clearunit_single(nd->bl.id, CLR_OUTSIGHT, sd->fd);
else
clif_spawn(&nd->bl);
}
nd->sc.option = option;
}
else {
if (flag&1) {
nd->sc.option &= ~OPTION_INVISIBLE;
clif_spawn(&nd->bl);
}
else if (flag&2)
nd->sc.option &= ~OPTION_HIDE;
else if (flag&4)
nd->sc.option |= OPTION_HIDE;
else if (flag&8)
nd->sc.option &= ~OPTION_CLOAK;
else if (flag&16)
nd->sc.option |= OPTION_CLOAK;
else { //Can't change the view_data to invisible class because the view_data for all npcs is shared! [Skotlex]
nd->sc.option |= OPTION_INVISIBLE;
clif_clearunit_area(&nd->bl,CLR_OUTSIGHT); // Hack to trick maya purple card [Xazax]
}
if (nd->class_ != JT_WARPNPC && nd->class_ != JT_GUILD_FLAG) //Client won't display option changes for these classes [Toms]
clif_changeoption(&nd->bl);
else {
if (nd->sc.option&(OPTION_HIDE|OPTION_INVISIBLE|OPTION_CLOAK))
clif_clearunit_area(&nd->bl,CLR_OUTSIGHT);
else
clif_spawn(&nd->bl);
}
map_foreachinmap(npc_cloaked_sub, nd->bl.m, BL_PC, nd->bl.id); // Because npc option has been updated we remove the npc id from sd->cloaked_npc
}
if (nd->class_ == JT_WARPNPC || nd->class_ == JT_GUILD_FLAG)
{ //Client won't display option changes for these classes [Toms]
if (nd->sc.option&(OPTION_HIDE|OPTION_INVISIBLE))
clif_clearunit_area(&nd->bl, CLR_OUTSIGHT);
else
clif_spawn(&nd->bl);
} else
clif_changeoption(&nd->bl);
if( flag&3 && (nd->u.scr.xs >= 0 || nd->u.scr.ys >= 0) )// check if player standing on a OnTouchArea
if( flag&11 && (nd->u.scr.xs >= 0 || nd->u.scr.ys >= 0) )// check if player standing on a OnTouchArea
map_foreachinallarea( npc_enable_sub, nd->bl.m, nd->bl.x-nd->u.scr.xs, nd->bl.y-nd->u.scr.ys, nd->bl.x+nd->u.scr.xs, nd->bl.y+nd->u.scr.ys, BL_PC, nd );
return true;
@ -1069,6 +1130,9 @@ int npc_touch_areanpc(struct map_session_data* sd, int16 m, int16 x, int16 y)
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) {
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))

View File

@ -1221,7 +1221,9 @@ const char *npc_get_script_event_name(int npce_index);
void npc_setcells(struct npc_data* nd);
void npc_unsetcells(struct npc_data* nd);
bool npc_movenpc(struct npc_data* nd, int16 x, int16 y);
bool npc_enable(const char* name, int flag);
bool npc_is_cloaked(struct npc_data* nd, struct map_session_data* sd);
bool npc_enable_target(const char* name, uint32 char_id, int flag);
#define npc_enable(name, flag) npc_enable_target(name, 0, flag)
void npc_setdisplayname(struct npc_data* nd, const char* newname);
void npc_setclass(struct npc_data* nd, short class_);
struct npc_data* npc_name2id(const char* name);

View File

@ -5783,6 +5783,16 @@ enum e_setpos pc_setpos(struct map_session_data* sd, unsigned short mapindex, in
}
channel_pcquit(sd,4); //quit map chan
// Remove Cloaked NPC if changing to another map
for (auto it = sd->cloaked_npc.begin(); it != sd->cloaked_npc.end(); ++it) {
block_list *npc_bl = map_id2bl(*it);
if (npc_bl && npc_bl->m != m) {
sd->cloaked_npc.erase(it);
break;
}
}
}
if( m < 0 )

View File

@ -648,6 +648,8 @@ struct map_session_data {
// Title system
std::vector<int> titles;
std::vector<int> cloaked_npc;
/* ShowEvent Data Cache flags from map */
bool *qi_display;
int qi_count;

View File

@ -24608,6 +24608,40 @@ BUILDIN_FUNC(convertpcinfo) {
return SCRIPT_CMD_SUCCESS;
}
BUILDIN_FUNC(cloakoffnpc)
{
if (npc_enable_target(script_getstr(st, 2), script_hasdata(st, 3) ? script_getnum(st, 3) : 0, 8))
return SCRIPT_CMD_SUCCESS;
return SCRIPT_CMD_FAILURE;
}
BUILDIN_FUNC(cloakonnpc)
{
if (npc_enable_target(script_getstr(st, 2), script_hasdata(st, 3) ? script_getnum(st, 3) : 0, 16))
return SCRIPT_CMD_SUCCESS;
return SCRIPT_CMD_FAILURE;
}
BUILDIN_FUNC(isnpccloaked)
{
struct npc_data *nd = npc_name2id(script_getstr(st, 2));
if (!nd) {
ShowError("buildin_isnpccloaked: %s is a non-existing NPC.\n", script_getstr(st, 2));
return SCRIPT_CMD_FAILURE;
}
map_session_data* sd;
if (!script_charid2sd(3, sd))
return SCRIPT_CMD_FAILURE;
script_pushint(st, npc_is_cloaked(nd, sd));
return SCRIPT_CMD_SUCCESS;
}
#include "../custom/script.inc"
// declarations that were supposed to be exported from npc_chat.cpp
@ -25280,6 +25314,9 @@ struct script_function buildin_func[] = {
BUILDIN_DEF(achievement_condition,"i"),
BUILDIN_DEF(getvariableofinstance,"ri"),
BUILDIN_DEF(convertpcinfo,"vi"),
BUILDIN_DEF(cloakoffnpc, "s?"),
BUILDIN_DEF(cloakonnpc, "s?"),
BUILDIN_DEF(isnpccloaked, "s?"),
#include "../custom/script_def.inc"
{NULL,NULL,NULL},