//===== Hercules Plugin =================================================================================================
//= npc masking
//===== By: =============================================================================================================
//= Garr
//===== Current Version: ================================================================================================
//= 1.0
//===== Compatible With: ================================================================================================
//= Hercules
//===== Description: ====================================================================================================
//= Masks NPCs for players based on their variables.
//===== Additional Comments: ============================================================================================
//= Usage:
//= npcmask <new sprite>,<int variable name>,<compare method>,<value1>,{<value 2>,{<new size>,{<new name>,{<npc name>}}}}
//= npcunmask {<npc name>}
//= Applies and removes NPC masking, accordingly.
//= <new sprite> - sprite NPC will show if conditions are met
//= <int variable name> - variable name to check, only integer character variables.
//= <compare method> - compare method, based on following masks:
//= 0x01 - Less, uses value1
//= 0x02 - Equal, uses value1
//= 0x04 - More, uses value1
//= 0x10 - "BETWEEN" - Between value1 and value2, including values 1 and 2
//= 0x20 - "EXCLUDE" - Excluding between value1 and value2, values 1 and 2 are excluded also.
//= Example: 0x03 - same as less or equal, makes a check (variable <= value1)
//= "BETWEEN" and "EXCLUDE" modes overwrite anything else, "EXCLUDE" will take precedence over all other.
//= <value1> - value1 to compare too, used in all compare methods.
//= <value2> - value2, used in "BETWEEN" and "EXCLUDE" modes. Optional parameter, if it won't be provided
//= while using "BETWEEN" or "EXCLUDE" methods, those methods will be skipped.
//= <new size> - size masking, defaults to medium.
//= <new name> - name masking, if not provided will use NPC's original name.
//= <npc name> - NPC name to use masking on. If not provided, will run on NPC script is running from.
//========================================================================================================================
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../map/chat.h"
#include "../map/clif.h"
#include "../map/map.h"
#include "../map/mob.h"
#include "../map/npc.h"
#include "../map/packets.h"
#include "../map/packets_struct.h"
#include "../map/path.h"
#include "../map/pc.h"
#include "../map/script.h"
#include "../map/status.h"
#include "../common/HPMi.h"
#include "../common/malloc.h"
#include "../common/mmo.h"
#include "../common/nullpo.h"
#include "../common/socket.h"
#include "../common/strlib.h"
#include "../common/utils.h"
#include "../common/HPMDataCheck.h" // should always be the last file included! (if you don't make it last, it'll intentionally break compile time)
HPExport struct hplugin_info pinfo = {
"npcmask", // Plugin name
SERVER_TYPE_MAP,// Which server types this plugin works with?
"0.1", // Plugin version
HPM_VERSION, // HPM Version (don't change, macro is automatically updated)
};
#define pcdb_checkid_sub(class_) \
( \
( (class_) < JOB_MAX_BASIC ) \
|| ( (class_) >= JOB_NOVICE_HIGH && (class_) <= JOB_DARK_COLLECTOR ) \
|| ( (class_) >= JOB_RUNE_KNIGHT && (class_) <= JOB_MECHANIC_T2 ) \
|| ( (class_) >= JOB_BABY_RUNE && (class_) <= JOB_BABY_MECHANIC2 ) \
|| ( (class_) >= JOB_SUPER_NOVICE_E && (class_) <= JOB_SUPER_BABY_E ) \
|| ( (class_) >= JOB_KAGEROU && (class_) <= JOB_OBORO ) \
|| ( (class_) >= JOB_REBELLION && (class_) < JOB_MAX ) \
)
#define pcdb_checkid(class_) pcdb_checkid_sub((unsigned int)(class_))
enum compare_method {
CM_NONE = 0x00,
CM_LESS = 0x01,
CM_EQU = 0x02,
CM_MORE = 0x04,
CM_BETWEEN = 0x10,
CM_EXCLUDE = 0x20,
CM_MAX = 0x37,
} c_m;
struct npc_mask {
unsigned masked : 1; // Bit to show that npc uses masking.
int cmask; //class_ mask
char nmask[NAME_LENGTH+1]; //name mask
unsigned smask : 2; // size mask
const char *varname; //Variable to check
enum compare_method compare; // Compare method
int value1,value2; // values to compare variable to
};
static inline void WBUFPOS(uint8* p, unsigned short pos, short x, short y, unsigned char dir) {
p += pos;
p[0] = (uint8)(x>>2);
p[1] = (uint8)((x<<6) | ((y>>4)&0x3f));
p[2] = (uint8)((y<<4) | (dir&0xf));
}
uint16 GetWord(uint32 val, int idx)
{
switch( idx )
{
case 0: return (uint16)( (val & 0x0000FFFF) );
case 1: return (uint16)( (val & 0xFFFF0000) >> 0x10 );
default:
return 0;
}
}
void clif_set_unit_idle2_hooked(struct block_list* bl, struct map_session_data *tsd, bool flag, int newclass) {
#if PACKETVER < 20091103
struct view_data* vd = status->get_viewdata(bl);
struct packet_idle_unit2 p;
struct npc_data *nd = ((TBL_NPC*)bl);
int g_id = status->get_guild_id(bl);
p.PacketType = idle_unit2Type;
#if PACKETVER >= 20071106
p.objecttype = pcdb_checkid(status->get_viewdata(bl)->class_)?0x0:0x6;;
#endif
p.GID = bl->id;
p.speed = status->get_speed(bl);
p.bodyState = 0;
p.healthState = 0;
p.effectState = nd->option;
p.job = flag? newclass:vd->class_;
p.head = vd->hair_style;
p.weapon = vd->weapon;
p.accessory = vd->head_bottom;
p.shield = vd->shield;
p.accessory2 = vd->head_top;
p.accessory3 = vd->head_mid;
if( bl->type == BL_NPC && ((!flag && vd->class_ == FLAG_CLASS) || (flag && newclass == FLAG_CLASS)) ) { //The hell, why flags work like this?
p.shield = status->get_emblem_id(bl);
p.accessory2 = GetWord(g_id, 1);
p.accessory3 = GetWord(g_id, 0);
}
p.headpalette = vd->hair_color;
p.bodypalette = vd->cloth_color;
p.headDir = 0;
p.GUID = g_id;
p.GEmblemVer = status->get_emblem_id(bl);
p.honor = 0;
p.virtue = 0;
p.isPKModeON = 0;
p.sex = vd->sex;
WBUFPOS(&p.PosDir[0],0,bl->x,bl->y,unit->getdir(bl));
p.xSize = p.ySize = 0;
p.state = vd->dead_sit;
p.clevel = 0;
clif->send(&p,sizeof(p),tsd?&tsd->bl:bl,SELF);
#else
return;
#endif
}
void clif_set_unit_idle_hooked(struct block_list* bl, struct map_session_data *tsd, bool flag, int newclass) {
if ( bl->type == BL_NPC) {
struct view_data* vd = status->get_viewdata(bl);
struct packet_idle_unit p;
struct npc_data *nd = ((TBL_NPC*)bl);
int g_id = status->get_guild_id(bl);
// nullpo_retv(bl);
#if PACKETVER < 20091103
clif_set_unit_idle2_hooked(bl,tsd,flag,newclass);
return;
#endif
p.PacketType = idle_unitType;
#if PACKETVER >= 20091103
p.PacketLength = sizeof(p);
p.objecttype = pcdb_checkid(status->get_viewdata(bl)->class_)?0x0:0x6;;
#endif
p.GID = bl->id;
p.speed = status->get_speed(bl);
p.bodyState = 0;
p.healthState = 0;
p.effectState = nd->option;
p.job = flag? newclass:vd->class_;
p.head = vd->hair_style;
p.weapon = vd->weapon;
p.accessory = vd->head_bottom;
#if PACKETVER < 7
p.shield = vd->shield;
#endif
p.accessory2 = vd->head_top;
p.accessory3 = vd->head_mid;
if( bl->type == BL_NPC && ((!flag && vd->class_ == FLAG_CLASS) || (flag && newclass == FLAG_CLASS)) ) { //The hell, why flags work like this?
p.accessory = status->get_emblem_id(bl);
p.accessory2 = GetWord(g_id, 1);
p.accessory3 = GetWord(g_id, 0);
}
p.headpalette = vd->hair_color;
p.bodypalette = vd->cloth_color;
p.headDir = 0;
#if PACKETVER >= 20101124
p.robe = vd->robe;
#endif
p.GUID = g_id;
p.GEmblemVer = status->get_emblem_id(bl);
p.honor = 0;
p.virtue = 0;
p.isPKModeON = 0;
p.sex = vd->sex;
WBUFPOS(&p.PosDir[0],0,bl->x,bl->y,unit->getdir(bl));
p.xSize = p.ySize = 0;
p.state = vd->dead_sit;
p.clevel = 0;
#if PACKETVER >= 20080102
p.font = 0;
#endif
#if PACKETVER >= 20150000 //actual 20120221
p.maxHP = -1;
p.HP = -1;
p.isBoss = 0;
#endif
clif->send(&p,sizeof(p),tsd?&tsd->bl:bl,SELF);
return;
}
return;
}
int packet_help(int packetid) {
int retval;
switch(packetid) {
case 0x0094:
retval = 2;
break;
case 0x0089:
#if PACKETVER >= 20040726
retval = 11;
#endif
#if PACKETVER >= 20040809
retval = 8;
#endif
break;
case 0x009b:
#if PACKETVER >= 20040906
retval = 10;
#endif
#if PACKETVER >= 20040920
retval = 6;
#endif
#if PACKETVER >= 20041005
retval = 11;
#endif
#if PACKETVER >= 20041025
retval = 6;
#endif
break;
case 0x008c:
#if PACKETVER >= 20041129
retval = 9;
#endif
#if PACKETVER >= 20050110
retval = 4;
#endif
#if PACKETVER >= 20050509
retval = 7;
#endif
#if PACKETVER >= 20050628
retval = 4;
#endif
#if PACKETVER >= 20050718
retval = 7;
#endif
#if PACKETVER >= 20050719
retval = 4;
#endif
#if PACKETVER >= 20060327
retval = 8;
#endif
#if PACKETVER >= 20070108
retval = 13;
#endif
#if PACKETVER >= 20070212
retval = 7;
#endif
#if PACKETVER >= 20080827
retval = 10;
#endif
break;
#if PACKETVER >= 20101124
case 0x0368: retval = 2; break;
#endif
#if PACKETVER >= 20111005
case 0x088a: retval = 2; break;
#endif
case 0x096A:
retval = 2;
#if PACKETVER >= 20120410
case 0x0889: retval = 2; break;
#endif
#if PACKETVER >= 20120702
case 0x094a: retval = 2; break;
#endif
#if PACKETVER >= 20130320
case 0x0898: retval = 2; break;
#endif
#if PACKETVER >= 20130522
case 0x08A6: retval = 2; break;
#endif
#if PACKETVER >= 20130529
case 0x0863: retval = 2; break;
#endif
#if PACKETVER >= 20130618
case 0x0944: retval = 2; break;
#endif
#if PACKETVER >= 20130814
case 0x0937: retval = 2; break;
#endif
#if PACKETVER >= 20131230
case 0x0926: retval = 2; break;
#endif
#if PACKETVER >= 20140115
case 0x0802: retval = 2; break;
#endif
#if PACKETVER >= 20140402
case 0x088A: retval = 2; break;
#endif
default: retval = 0; break;
}
return retval;
}
void clif_parse_GetCharNameRequest_prehook(int *fd, struct map_session_data *sd) {
int id = RFIFOL(*fd,packet_help(RFIFOW(*fd,0))); //packet_db[RFIFOW(*fd,0)].pos[0] is not working for some reason...
struct block_list* bl;
if( id < 0 && -id == sd->bl.id ) // for disguises [Valaris]
id = sd->bl.id;
bl = map->id2bl(id);
if( bl == NULL )
return; // Lagged clients could request names of already gone mobs/players. [Skotlex]
if( sd->bl.m != bl->m || !check_distance_bl(&sd->bl, bl, AREA_SIZE) )
return; // Block namerequests past view range
if (bl->type != BL_NPC)
return;
unsigned char buf[103];
struct npc_data *nd = (TBL_NPC*)bl;;
struct npc_mask *nm;
int var;
bool flag = false;
// nullpo_retv(bl);
WBUFW(buf,0) = 0x95;
WBUFL(buf,2) = bl->id;
if ( !(nm = getFromNPCD(nd,0)) ) {
CREATE(nm,struct npc_mask,1);
safestrncpy(nm->nmask, "\0", NAME_LENGTH);
nm->cmask = 0;
nm->smask = 0;
nm->varname = "\0";
nm->compare = CM_NONE;
nm->value1 = nm->value2 = 0;
nm->masked = 0;
addToNPCD(nd,nm,0,true);
}
var = pc_readglobalreg(sd, script->add_str(nm->varname));
if ( nm->masked && nm->compare) { //Not checking var or nm->value1/2 to make usage of variables resulting in 0 possible, or to compare with 0.
if (nm->compare&CM_LESS && var < nm->value1)
flag = true;
if (nm->compare&CM_EQU && var == nm->value1)
flag = true;
if (nm->compare&CM_MORE && var > nm->value1)
flag = true;
if ( nm->compare&CM_BETWEEN && var >= nm->value1 && var <= nm->value2 )
flag = true;
if ( nm->compare&CM_EXCLUDE && (var < nm->value1 || var > nm->value2) )
flag = true;
}
if (flag && nm->nmask != "\0") {
memcpy(WBUFP(buf,6), nm->nmask, NAME_LENGTH);
} else {
memcpy(WBUFP(buf,6), ((TBL_NPC*)bl)->name, NAME_LENGTH);
}
WFIFOHEAD(sd->fd, 30);
memcpy(WFIFOP(sd->fd, 0), buf, 30);
WFIFOSET(sd->fd, 30);
hookStop();
return;
}
void clif_getareachar_unit_prehook (struct map_session_data* sd,struct block_list *bl) {
if (bl->type != BL_NPC)
return;
struct view_data *vd = NULL;
struct npc_mask *nm;
struct npc_data *nd = (TBL_NPC*)bl;
int var;
bool flag = false;
vd = status->get_viewdata(bl);
if (!vd || vd->class_ == INVISIBLE_CLASS)
return;
if(bl->type == BL_NPC && !nd->chat_id && (nd->option&OPTION_INVISIBLE))
return;
if ( !(nm = getFromNPCD(nd,0)) ) {
CREATE(nm,struct npc_mask,1);
safestrncpy(nm->nmask, "\0", NAME_LENGTH);
nm->cmask = 0;
nm->smask = 0;
nm->varname = "\0";
nm->compare = CM_NONE;
nm->value1 = nm->value2 = 0;
nm->masked = 0;
addToNPCD(nd,nm,0,true);
}
var = pc_readglobalreg(sd, script->add_str(nm->varname));
if ( nm->masked && nm->compare) { //Not checking var or nm->value1/2 to make usage of variables resulting in 0 possible, or to compare with 0.
if ( nm->compare&CM_LESS && var < nm->value1 )
flag = true;
if ( nm->compare&CM_EQU && var == nm->value1 )
flag = true;
if ( nm->compare&CM_MORE && var > nm->value1 )
flag = true;
if ( nm->compare&CM_BETWEEN && var >= nm->value1 && var <= nm->value2 )
flag = true;
if ( nm->compare&CM_EXCLUDE && (var < nm->value1 || var > nm->value2 ) )
flag = true;
}
clif_set_unit_idle_hooked(bl,sd,flag,nm->cmask);
if( nd->chat_id )
clif->dispchat((struct chat_data*)map->id2bl(nd->chat_id),sd->fd);
if( (!flag && nd->size == SZ_BIG) || (flag && nm->smask == SZ_BIG) )
clif->specialeffect_single(bl,423,sd->fd);
else if( (!flag && nd->size == 1) || (flag && nm->smask == 1) )
clif->specialeffect_single(bl,421,sd->fd);
hookStop();
return;
}
BUILDIN(npcmask) {
struct npc_mask *nm;
struct npc_data *nd;
char *varn;
const char *varn2;
if ( script_hasdata(st,9) && script_isstringtype(st,9) ) {
nd = npc->name2id(script_getstr(st,9));
} else {
nd = map->id2nd(st->oid);
}
if( nd == NULL ) {
script_pushint(st,-1); //No such NPC;
return true;
}
varn = (char*)script_getstr(st,3);
if ( !not_server_variable(varn[0]) || is_string_variable(varn) ) {
ShowWarning("buildin_npcmask: Server-side or string-type variable name used. Aborting.\n");
return true;
}
if ( !(nm = getFromNPCD(nd,0)) ) {
CREATE(nm,struct npc_mask,1);
//assigning struct from given variables
nm->varname = script_getstr(st,3);
nm->cmask = script_getnum(st,2);
nm->compare = CM_NONE;
if ( script_getnum(st,4)&CM_LESS) nm->compare |= CM_LESS;
if ( script_getnum(st,4)&CM_EQU) nm->compare |= CM_EQU;
if ( script_getnum(st,4)&CM_MORE) nm->compare |= CM_MORE;
if ( script_getnum(st,4)&CM_BETWEEN) {
if ( script_hasdata(st,6) ) {
nm->compare = CM_BETWEEN;
} else {
ShowWarning("buildin_npcmask: Compare method \"BETWEEN\" was chosen, but no value2 provided. Skipping.\n");
}
}
if ( script_getnum(st,4)&CM_EXCLUDE) {
if ( script_hasdata(st,6) ) {
nm->compare = CM_EXCLUDE;
} else {
ShowWarning("buildin_npcmask: Compare method \"EXCLUDE\" was chosen, but no value2 provided. Skipping.\n");
}
}
nm->value1 = script_getnum(st,5);
if ( script_hasdata(st,6) ) {
if ( script_isinttype(st,6) ) {
nm->value2 = script_getnum(st,6);
} else {
ShowWarning("buildin_npcmask: Provided value2 is not of int type, defaulting to 0.\n");
nm->value2 = 0;
}
if ( nm->value2 < nm->value1 ) {
int temp;
temp = nm->value1;
nm->value1 = nm->value2;
nm->value2 = temp;
}
} else {
nm->value2 = 0;
}
if ( script_hasdata(st,7) ) {
if ( script_isinttype(st,7) ) {
if ( script_getnum(st,7) < 0 || script_getnum(st,7) > 2 ) {
ShowWarning("builin_npcmask: Provided size is unknown, defaulting to medium.\n");
nm->smask = 0;
}
nm->smask = script_getnum(st,7);
} else {
ShowWarning("buildin_npcmask: Provided size is in wrong format, defaulting to medium.\n");
nm->smask = 0;
}
} else {
nm->smask = 0;
}
if ( script_hasdata(st,8) ) {
if ( script_isstringtype(st,8) ) {
varn2 = script_getstr(st,8);
safestrncpy(nm->nmask, varn2, NAME_LENGTH);
}
} else {
safestrncpy(nm->nmask, "\0", NAME_LENGTH);
}
nm->masked = 1;
addToNPCD(nd,nm,0,true);
} else {
nm->varname = script_getstr(st,3);
nm->cmask = script_getnum(st,2);
nm->compare = CM_NONE;
if ( script_getnum(st,4)&CM_LESS) nm->compare |= CM_LESS;
if ( script_getnum(st,4)&CM_EQU) nm->compare |= CM_EQU;
if ( script_getnum(st,4)&CM_MORE) nm->compare |= CM_MORE;
if ( script_getnum(st,4)&CM_BETWEEN) {
if ( script_hasdata(st,6) ) {
nm->compare = CM_BETWEEN;
} else {
ShowWarning("buildin_npcmask: Compare method \"BETWEEN\" was chosen, but no value2 provided. Skipping.\n");
}
}
if ( script_getnum(st,4)&CM_EXCLUDE) {
if ( script_hasdata(st,6) ) {
nm->compare = CM_EXCLUDE;
} else {
ShowWarning("buildin_npcmask: Compare method \"EXCLUDE\" was chosen, but no value2 provided. Skipping.\n");
}
}
nm->value1 = script_getnum(st,5);
if ( script_hasdata(st,6) ) {
if ( script_isinttype(st,6) ) {
nm->value2 = script_getnum(st,6);
} else {
ShowWarning("buildin_npcmask: Provided value2 is not of int type, defaulting to 0.\n");
nm->value2 = 0;
}
if ( nm->value2 < nm->value1 ) {
int temp;
temp = nm->value1;
nm->value1 = nm->value2;
nm->value2 = temp;
}
} else {
nm->value2 = 0;
}
if ( script_hasdata(st,7) ) {
if ( script_isinttype(st,7) ) {
if ( script_getnum(st,7) < 0 || script_getnum(st,7) > 2 ) {
ShowWarning("builin_npcmask: Provided size is unknown, defaulting to medium.\n");
nm->smask = 0;
}
nm->smask = script_getnum(st,7);
} else {
ShowWarning("buildin_npcmask: Provided size is in wrong format, defaulting to medium.\n");
nm->smask = 0;
}
} else {
nm->smask = 0;
}
if ( script_hasdata(st,8) ) {
if ( script_isstringtype(st,8) ) {
varn2 = script_getstr(st,8);
safestrncpy(nm->nmask, varn2, NAME_LENGTH);
}
} else {
safestrncpy(nm->nmask, "\0", NAME_LENGTH);
}
nm->masked = 1;
}
script_pushint(st,1);
return true;
}
BUILDIN(npcunmask) {
struct npc_mask *nm;
struct npc_data *nd;
if ( script_hasdata(st,2) ) {
nd = npc->name2id(script_getstr(st,2));
} else {
nd = map->id2nd(st->oid);
}
if( nd == NULL ) {
script_pushint(st,-1); //No such NPC;
return true;
}
if ( !(nm = getFromNPCD(nd,0)) ) {
CREATE(nm,struct npc_mask,1);
safestrncpy(nm->nmask, "\0", NAME_LENGTH);
nm->cmask = 0;
nm->smask = 0;
nm->varname = "\0";
nm->compare = CM_NONE;
nm->value1 = nm->value2 = 0;
nm->masked = 0;
addToNPCD(nd,nm,0,true);
} else {
safestrncpy(nm->nmask, "\0", NAME_LENGTH);
nm->cmask = 0;
nm->smask = 0;
nm->varname = "\0";
nm->compare = CM_NONE;
nm->value1 = nm->value2 = 0;
nm->masked = 0;
}
script_pushint(st,1);
return true;
}
HPExport void plugin_init (void) {
battle = GET_SYMBOL("battle");
clif = GET_SYMBOL("clif");
iMalloc = GET_SYMBOL("iMalloc");
map = GET_SYMBOL("map");
mapit = GET_SYMBOL("mapit");
npc = GET_SYMBOL("npc");
path = GET_SYMBOL("path");
pc = GET_SYMBOL("pc");
script = GET_SYMBOL("script");
session = GET_SYMBOL("session");
sockt = GET_SYMBOL("sockt");
status = GET_SYMBOL("status");
strlib = GET_SYMBOL("strlib");
unit = GET_SYMBOL("unit");
addScriptCommand( "npcmask", "isii????", npcmask );
addScriptCommand( "npcunmask", "?", npcunmask );
addHookPre("clif->getareachar_unit", clif_getareachar_unit_prehook );
addHookPre("clif->pGetCharNameRequest",clif_parse_GetCharNameRequest_prehook);
}