/**
* This file is part of Hercules.
* http://herc.ws - http://github.com/HerculesWS/Hercules
*
* Copyright (C) 2012-2020 Hercules Dev Team
* Copyright (C) Athena Dev Teams
*
* Hercules is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#define HERCULES_CORE
#include "config/core.h" // ANTI_MAYAP_CHEAT, RENEWAL, SECURE_NPCTIMEOUT
#include "clif.h"
#include "map/atcommand.h"
#include "map/battle.h"
#include "map/battleground.h"
#include "map/channel.h"
#include "map/chat.h"
#include "map/chrif.h"
#include "map/clan.h"
#include "map/elemental.h"
#include "map/guild.h"
#include "map/homunculus.h"
#include "map/instance.h"
#include "map/intif.h"
#include "map/irc-bot.h"
#include "map/itemdb.h"
#include "map/log.h"
#include "map/mail.h"
#include "map/map.h"
#include "map/mercenary.h"
#include "map/messages.h"
#include "map/mob.h"
#include "map/npc.h"
#include "map/party.h"
#include "map/pc.h"
#include "map/pet.h"
#include "map/quest.h"
#include "map/rodex.h"
#include "map/refine.h"
#include "map/script.h"
#include "map/skill.h"
#include "map/status.h"
#include "map/stylist.h"
#include "map/storage.h"
#include "map/trade.h"
#include "map/unit.h"
#include "map/vending.h"
#include "map/achievement.h"
#include "common/HPM.h"
#include "common/cbasetypes.h"
#include "common/conf.h"
#include "common/ers.h"
#include "common/grfio.h"
#include "common/memmgr.h"
#include "common/mmo.h" // NEW_CARTS, char_achievements
#include "common/nullpo.h"
#include "common/packets.h"
#include "common/random.h"
#include "common/showmsg.h"
#include "common/socket.h"
#include "common/strlib.h"
#include "common/timer.h"
#include "common/utils.h"
#include
#include
#include
#include
#include
static struct clif_interface clif_s;
struct clif_interface *clif;
static struct s_packet_db packet_db[MAX_PACKET_DB + 1];
/* re-usable */
static struct packet_itemlist_normal itemlist_normal;
static struct packet_itemlist_equip itemlist_equip;
static struct ZC_STORE_ITEMLIST_NORMAL storelist_normal;
static struct ZC_STORE_ITEMLIST_EQUIP storelist_equip;
static struct packet_viewequip_ack viewequip_list;
// temporart buffer for send big packets
char packet_buf[0xffff];
//#define DUMP_UNKNOWN_PACKET
//#define DUMP_INVALID_PACKET
//Converts item type in case of pet eggs.
static inline int itemtype(int type)
{
switch( type ) {
#if PACKETVER >= 20080827
case IT_WEAPON:
return IT_ARMOR;
case IT_ARMOR:
case IT_PETARMOR:
#endif
case IT_PETEGG:
return IT_WEAPON;
default:
return type;
}
}
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));
}
// client-side: x0+=sx0*0.0625-0.5 and y0+=sy0*0.0625-0.5
static inline void WBUFPOS2(uint8 *p, unsigned short pos, short x0, short y0, short x1, short y1, unsigned char sx0, unsigned char sy0)
{
p += pos;
p[0] = (uint8)(x0>>2);
p[1] = (uint8)((x0<<6) | ((y0>>4)&0x3f));
p[2] = (uint8)((y0<<4) | ((x1>>6)&0x0f));
p[3] = (uint8)((x1<<2) | ((y1>>8)&0x03));
p[4] = (uint8)y1;
p[5] = (uint8)((sx0<<4) | (sy0&0x0f));
}
#if 0 // Currently unused
static inline void WFIFOPOS(int fd, unsigned short pos, short x, short y, unsigned char dir)
{
WBUFPOS(WFIFOP(fd,pos), 0, x, y, dir);
}
#endif // 0
static inline void WFIFOPOS2(int fd, unsigned short pos, short x0, short y0, short x1, short y1, unsigned char sx0, unsigned char sy0)
{
WBUFPOS2(WFIFOP(fd,pos), 0, x0, y0, x1, y1, sx0, sy0);
}
static inline void RBUFPOS(const uint8 *p, unsigned short pos, short *x, short *y, unsigned char *dir)
{
p += pos;
if( x ) {
x[0] = ( ( p[0] & 0xff ) << 2 ) | ( p[1] >> 6 );
}
if( y ) {
y[0] = ( ( p[1] & 0x3f ) << 4 ) | ( p[2] >> 4 );
}
if( dir ) {
dir[0] = ( p[2] & 0x0f );
}
}
static inline void RFIFOPOS(int fd, unsigned short pos, short *x, short *y, unsigned char *dir)
{
RBUFPOS(RFIFOP(fd,pos), 0, x, y, dir);
}
#if 0 // currently unused
static inline void RBUFPOS2(const uint8 *p, unsigned short pos, short *x0, short *y0, short *x1, short *y1, unsigned char *sx0, unsigned char *sy0)
{
p += pos;
if( x0 ) {
x0[0] = ( ( p[0] & 0xff ) << 2 ) | ( p[1] >> 6 );
}
if( y0 ) {
y0[0] = ( ( p[1] & 0x3f ) << 4 ) | ( p[2] >> 4 );
}
if( x1 ) {
x1[0] = ( ( p[2] & 0x0f ) << 6 ) | ( p[3] >> 2 );
}
if( y1 ) {
y1[0] = ( ( p[3] & 0x03 ) << 8 ) | ( p[4] >> 0 );
}
if( sx0 ) {
sx0[0] = ( p[5] & 0xf0 ) >> 4;
}
if( sy0 ) {
sy0[0] = ( p[5] & 0x0f ) >> 0;
}
}
static inline void RFIFOPOS2(int fd, unsigned short pos, short *x0, short *y0, short *x1, short *y1, unsigned char *sx0, unsigned char *sy0)
{
RBUFPOS2(WFIFOP(fd,pos), 0, x0, y0, x1, y1, sx0, sy0);
}
#endif // 0
//To identify disguised characters.
static bool clif_isdisguised(struct block_list *bl)
{
struct map_session_data *sd = BL_CAST(BL_PC, bl);
if (sd == NULL || sd->disguise == -1)
return false;
return true;
}
/*==========================================
* Ip setting of map-server
*------------------------------------------*/
static bool clif_setip(const char *ip)
{
char ip_str[16];
nullpo_retr(false, ip);
clif->map_ip = sockt->host2ip(ip);
if ( !clif->map_ip ) {
ShowWarning("Failed to Resolve Map Server Address! (%s)\n", ip);
return false;
}
safestrncpy(clif->map_ip_str, ip, sizeof(clif->map_ip_str));
ShowInfo("Map Server IP Address : '"CL_WHITE"%s"CL_RESET"' -> '"CL_WHITE"%s"CL_RESET"'.\n", ip, sockt->ip2str(clif->map_ip, ip_str));
return true;
}
static bool clif_setbindip(const char *ip)
{
nullpo_retr(false, ip);
clif->bind_ip = sockt->host2ip(ip);
if ( clif->bind_ip ) {
char ip_str[16];
ShowInfo("Map Server Bind IP Address : '"CL_WHITE"%s"CL_RESET"' -> '"CL_WHITE"%s"CL_RESET"'.\n", ip, sockt->ip2str(clif->bind_ip, ip_str));
return true;
}
ShowWarning("Failed to Resolve Map Server Address! (%s)\n", ip);
return false;
}
/*==========================================
* Sets map port to 'port'
* is run from map.c upon loading map server configuration
*------------------------------------------*/
static void clif_setport(uint16 port)
{
clif->map_port = port;
}
#if 0 // Unused function
/*==========================================
* Returns map server IP
*------------------------------------------*/
static uint32 clif_getip(void)
{
return clif->map_ip;
}
#endif // 0
#if 0 // Unused function
/*==========================================
* Returns map port which is set by clif_setport()
*------------------------------------------*/
static uint16 clif_getport(void)
{
return clif->map_port;
}
#endif // 0
/*==========================================
* Updates server ip resolution and returns it
*------------------------------------------*/
static uint32 clif_refresh_ip(void)
{
uint32 new_ip = sockt->host2ip(clif->map_ip_str);
if ( new_ip && new_ip != clif->map_ip ) {
clif->map_ip = new_ip;
ShowInfo("Updating IP resolution of [%s].\n", clif->map_ip_str);
return clif->map_ip;
}
return 0;
}
static unsigned char clif_bl_type(struct block_list *bl)
{
#if PACKETVER >= 20071106
struct view_data *vd;
nullpo_retr(CLUT_NPC, bl);
switch (bl->type) {
case BL_PC:
vd = status->get_viewdata(bl);
nullpo_retr(CLUT_NPC, vd);
if (clif->isdisguised(bl) && !pc->db_checkid(vd->class))
return CLUT_NPC;
return CLUT_PC;
case BL_ITEM:
return CLUT_ITEM;
case BL_SKILL:
return CLUT_SKILL;
case BL_CHAT:
return CLUT_UNKNOWN;
case BL_MOB:
vd = status->get_viewdata(bl);
nullpo_retr(CLUT_NPC, vd);
return pc->db_checkid(vd->class) ? CLUT_PC : CLUT_MOB;
case BL_NPC:
vd = status->get_viewdata(bl);
nullpo_retr(CLUT_NPC, vd);
#if PACKETVER >= 20170726
return CLUT_EVENT;
#else
return pc->db_checkid(vd->class) ? CLUT_PC : CLUT_EVENT;
#endif
case BL_PET:
vd = status->get_viewdata(bl);
nullpo_retr(CLUT_NPC, vd);
return pc->db_checkid(vd->class) ? CLUT_PC : CLUT_PET;
case BL_HOM:
return CLUT_HOMNUCLUS;
case BL_MER:
return CLUT_MERCNARY;
case BL_ELEM:
return CLUT_ELEMENTAL;
default:
return CLUT_NPC;
}
#endif
return CLUT_UNKNOWN;
}
/*==========================================
* sub process of clif_send
* Called from a map_foreachinarea (grabs all players in specific area and subjects them to this function)
* In order to send area-wise packets, such as:
* - AREA : everyone nearby your area
* - AREA_WOSC (AREA WITHOUT SAME CHAT) : Not run for people in the same chat as yours
* - AREA_WOC (AREA WITHOUT CHAT) : Not run for people inside a chat
* - AREA_WOS (AREA WITHOUT SELF) : Not run for self
* - AREA_CHAT_WOC : Everyone in the area of your chat without a chat
*------------------------------------------*/
static int clif_send_sub(struct block_list *bl, va_list ap)
{
struct block_list *src_bl;
struct map_session_data *sd;
void *buf;
int len, type, fd;
nullpo_ret(bl);
Assert_ret(bl->type == BL_PC);
sd = BL_UCAST(BL_PC, bl);
fd = sd->fd;
if (!fd || sockt->session[fd] == NULL) //Don't send to disconnected clients.
return 0;
buf = va_arg(ap,void*);
len = va_arg(ap,int);
nullpo_ret(src_bl = va_arg(ap,struct block_list*));
type = va_arg(ap,int);
switch(type) {
case AREA_WOS:
if (bl == src_bl)
return 0;
break;
case AREA_WOC:
if (sd->chat_id != 0 || bl == src_bl)
return 0;
break;
case AREA_WOSC: {
if (src_bl->type == BL_PC) {
const struct map_session_data *ssd = BL_UCCAST(BL_PC, src_bl);
if (ssd != NULL && sd->chat_id != 0 && (sd->chat_id == ssd->chat_id))
return 0;
} else if (src_bl->type == BL_NPC) {
const struct npc_data *nd = BL_UCCAST(BL_NPC, src_bl);
if (nd != NULL && sd->chat_id != 0 && (sd->chat_id == nd->chat_id))
return 0;
}
}
break;
/* 0x120 crashes the client when warping for this packetver range [Ind/Hercules], thanks to Judas! */
#if PACKETVER > 20120418 && PACKETVER < 20130000
case AREA:
if( WBUFW(buf, 0) == 0x120 && sd->state.warping )
return 0;
break;
#endif
}
/* unless visible, hold it here */
if( clif->ally_only && !sd->sc.data[SC_CLAIRVOYANCE] && !sd->special_state.intravision && battle->check_target( src_bl, &sd->bl, BCT_ENEMY ) > 0 )
return 0;
return clif->send_actual(fd, buf, len);
}
static int clif_send_actual(int fd, void *buf, int len)
{
nullpo_retr(0, buf);
WFIFOHEAD(fd, len);
if (WFIFOP(fd,0) == buf) {
ShowError("WARNING: Invalid use of clif->send function\n");
ShowError(" Packet x%4x use a WFIFO of a player instead of to use a buffer.\n", WBUFW(buf,0));
ShowError(" Please correct your code.\n");
// don't send to not move the pointer of the packet for next sessions in the loop
//WFIFOSET(fd,0);//## TODO is this ok?
//NO. It is not ok. There is the chance WFIFOSET actually sends the buffer data, and shifts elements around, which will corrupt the buffer.
return 0;
}
memcpy(WFIFOP(fd,0), buf, len);
WFIFOSET(fd,len);
return 0;
}
/*==========================================
* Packet Delegation (called on all packets that require data to be sent to more than one client)
* functions that are sent solely to one use whose ID it posses use WFIFOSET
*------------------------------------------*/
static bool clif_send(const void *buf, int len, struct block_list *bl, enum send_target type)
{
if (type != ALL_CLIENT)
nullpo_retr(false, bl);
nullpo_retr(false, buf);
Assert_retr(false, len > 0);
int i;
struct map_session_data *sd = BL_CAST(BL_PC, bl), *tsd;
struct party_data *p = NULL;
struct guild *g = NULL;
struct battleground_data *bgd = NULL;
int x0 = 0, x1 = 0, y0 = 0, y1 = 0, fd;
struct s_mapiterator* iter;
int area_size;
if (sd != NULL && pc_isinvisible(sd)) {
if (type == AREA || type == BG || type == BG_AREA)
type = SELF;
else if (type == AREA_WOS || type == BG_WOS || type == BG_AREA_WOS)
return true;
}
switch(type) {
case ALL_CLIENT: //All player clients.
iter = mapit_getallusers();
while ((tsd = BL_UCAST(BL_PC, mapit->next(iter))) != NULL) {
WFIFOHEAD(tsd->fd, len);
memcpy(WFIFOP(tsd->fd,0), buf, len);
WFIFOSET(tsd->fd,len);
}
mapit->free(iter);
break;
case ALL_SAMEMAP: //All players on the same map
iter = mapit_getallusers();
while ((tsd = BL_UCAST(BL_PC, mapit->next(iter))) != NULL) {
if (bl && bl->m == tsd->bl.m) {
WFIFOHEAD(tsd->fd, len);
memcpy(WFIFOP(tsd->fd,0), buf, len);
WFIFOSET(tsd->fd,len);
}
}
mapit->free(iter);
break;
case AREA:
case AREA_WOSC:
case AREA_DEAD:
if (sd && bl->prev == NULL) //Otherwise source misses the packet.[Skotlex]
clif->send (buf, len, bl, SELF);
/* Fall through */
case AREA_WOC:
case AREA_WOS:
if (type == AREA_DEAD)
area_size = DEAD_AREA_SIZE;
else
area_size = AREA_SIZE;
nullpo_retr(true, bl);
map->foreachinarea(clif->send_sub, bl->m, bl->x - area_size, bl->y - area_size, bl->x + area_size, bl->y + area_size,
BL_PC, buf, len, bl, type);
break;
case AREA_CHAT_WOC:
nullpo_retr(true, bl);
map->foreachinarea(clif->send_sub, bl->m, bl->x-CHAT_AREA_SIZE, bl->y-CHAT_AREA_SIZE,
bl->x+CHAT_AREA_SIZE, bl->y+CHAT_AREA_SIZE, BL_PC, buf, len, bl, AREA_WOC);
break;
case CHAT:
case CHAT_WOS:
nullpo_retr(true, bl);
{
const struct chat_data *cd = NULL;
if (sd != NULL) {
cd = map->id2cd(sd->chat_id);
} else {
cd = BL_CCAST(BL_CHAT, bl);
}
if (cd == NULL)
break;
for(i = 0; i < cd->users; i++) {
if (type == CHAT_WOS && cd->usersd[i] == sd)
continue;
if ((fd=cd->usersd[i]->fd) >0 && sockt->session[fd]) { // Added check to see if session exists [PoW]
WFIFOHEAD(fd,len);
memcpy(WFIFOP(fd,0), buf, len);
WFIFOSET(fd,len);
}
}
}
break;
case PARTY_AREA:
case PARTY_AREA_WOS:
nullpo_retr(true, bl);
x0 = bl->x - AREA_SIZE;
y0 = bl->y - AREA_SIZE;
x1 = bl->x + AREA_SIZE;
y1 = bl->y + AREA_SIZE;
/* Fall through */
case PARTY:
case PARTY_WOS:
case PARTY_SAMEMAP:
case PARTY_SAMEMAP_WOS:
if (sd && sd->status.party_id)
p = party->search(sd->status.party_id);
if (p) {
for(i=0;idata[i].sd) == NULL )
continue;
if( !(fd=sd->fd) )
continue;
if( sd->bl.id == bl->id && (type == PARTY_WOS || type == PARTY_SAMEMAP_WOS || type == PARTY_AREA_WOS) )
continue;
if( type != PARTY && type != PARTY_WOS && bl->m != sd->bl.m )
continue;
if( (type == PARTY_AREA || type == PARTY_AREA_WOS) && (sd->bl.x < x0 || sd->bl.y < y0 || sd->bl.x > x1 || sd->bl.y > y1) )
continue;
WFIFOHEAD(fd,len);
memcpy(WFIFOP(fd,0), buf, len);
WFIFOSET(fd,len);
}
if (!map->enable_spy) //Skip unnecessary parsing. [Skotlex]
break;
iter = mapit_getallusers();
while ((tsd = BL_UCAST(BL_PC, mapit->next(iter))) != NULL) {
if( tsd->partyspy == p->party.party_id ) {
WFIFOHEAD(tsd->fd, len);
memcpy(WFIFOP(tsd->fd,0), buf, len);
WFIFOSET(tsd->fd,len);
}
}
mapit->free(iter);
}
break;
case DUEL:
case DUEL_WOS:
if (!sd || !sd->duel_group) break; //Invalid usage.
iter = mapit_getallusers();
while ((tsd = BL_UCAST(BL_PC, mapit->next(iter))) != NULL) {
if( type == DUEL_WOS && bl->id == tsd->bl.id )
continue;
if( sd->duel_group == tsd->duel_group ) {
WFIFOHEAD(tsd->fd, len);
memcpy(WFIFOP(tsd->fd,0), buf, len);
WFIFOSET(tsd->fd,len);
}
}
mapit->free(iter);
break;
case SELF:
if (sd && (fd=sd->fd) != 0) {
WFIFOHEAD(fd,len);
memcpy(WFIFOP(fd,0), buf, len);
WFIFOSET(fd,len);
}
break;
// New definitions for guilds [Valaris] - Cleaned up and reorganized by [Skotlex]
case GUILD_AREA:
case GUILD_AREA_WOS:
nullpo_retr(true, bl);
x0 = bl->x - AREA_SIZE;
y0 = bl->y - AREA_SIZE;
x1 = bl->x + AREA_SIZE;
y1 = bl->y + AREA_SIZE;
/* Fall through */
case GUILD_SAMEMAP:
case GUILD_SAMEMAP_WOS:
case GUILD:
case GUILD_WOS:
case GUILD_NOBG:
if (sd && sd->status.guild_id)
g = sd->guild;
if (g) {
for(i = 0; i < g->max_member; i++) {
if( (sd = g->member[i].sd) != NULL ) {
if( !(fd=sd->fd) )
continue;
if( type == GUILD_NOBG && sd->bg_id )
continue;
if( sd->bl.id == bl->id && (type == GUILD_WOS || type == GUILD_SAMEMAP_WOS || type == GUILD_AREA_WOS) )
continue;
if( type != GUILD && type != GUILD_NOBG && type != GUILD_WOS && sd->bl.m != bl->m )
continue;
if( (type == GUILD_AREA || type == GUILD_AREA_WOS) && (sd->bl.x < x0 || sd->bl.y < y0 || sd->bl.x > x1 || sd->bl.y > y1) )
continue;
WFIFOHEAD(fd,len);
memcpy(WFIFOP(fd,0), buf, len);
WFIFOSET(fd,len);
}
}
if (!map->enable_spy) //Skip unnecessary parsing. [Skotlex]
break;
iter = mapit_getallusers();
while ((tsd = BL_UCAST(BL_PC, mapit->next(iter))) != NULL) {
if( tsd->guildspy == g->guild_id ) {
WFIFOHEAD(tsd->fd, len);
memcpy(WFIFOP(tsd->fd,0), buf, len);
WFIFOSET(tsd->fd,len);
}
}
mapit->free(iter);
}
break;
case BG_AREA:
case BG_AREA_WOS:
nullpo_retr(true, bl);
x0 = bl->x - AREA_SIZE;
y0 = bl->y - AREA_SIZE;
x1 = bl->x + AREA_SIZE;
y1 = bl->y + AREA_SIZE;
/* Fall through */
case BG_SAMEMAP:
case BG_SAMEMAP_WOS:
case BG:
case BG_WOS:
if( sd && sd->bg_id && (bgd = bg->team_search(sd->bg_id)) != NULL ) {
for( i = 0; i < MAX_BG_MEMBERS; i++ ) {
if( (sd = bgd->members[i].sd) == NULL || !(fd = sd->fd) )
continue;
if( sd->bl.id == bl->id && (type == BG_WOS || type == BG_SAMEMAP_WOS || type == BG_AREA_WOS) )
continue;
if( type != BG && type != BG_WOS && sd->bl.m != bl->m )
continue;
if( (type == BG_AREA || type == BG_AREA_WOS) && (sd->bl.x < x0 || sd->bl.y < y0 || sd->bl.x > x1 || sd->bl.y > y1) )
continue;
WFIFOHEAD(fd,len);
memcpy(WFIFOP(fd,0), buf, len);
WFIFOSET(fd,len);
}
}
break;
case BG_QUEUE:
if( sd && sd->bg_queue.arena ) {
struct script_queue *queue = script->queue(sd->bg_queue.arena->queue_id);
for (i = 0; i < VECTOR_LENGTH(queue->entries); i++) {
struct map_session_data *qsd = map->id2sd(VECTOR_INDEX(queue->entries, i));
if (qsd != NULL) {
WFIFOHEAD(qsd->fd,len);
memcpy(WFIFOP(qsd->fd,0), buf, len);
WFIFOSET(qsd->fd,len);
}
}
}
break;
case CLAN:
if (sd && sd->status.clan_id) {
struct clan *c = clan->search(sd->status.clan_id);
nullpo_retr(false, c);
for (i = 0; i < VECTOR_LENGTH(c->members); i++) {
if (VECTOR_INDEX(c->members, i).online == 0 || (sd = VECTOR_INDEX(c->members, i).sd) == NULL || (fd = sd->fd) <= 0)
continue;
WFIFOHEAD(fd, len);
memcpy(WFIFOP(fd, 0), buf, len);
WFIFOSET(fd, len);
}
}
break;
default:
ShowError("clif_send: Unrecognized type %u\n", type);
return false;
}
return true;
}
/// Notifies the client, that it's connection attempt was accepted.
/// 0073 .L .3B .B .B (ZC_ACCEPT_ENTER)
/// 02eb .L .3B .B .B .W (ZC_ACCEPT_ENTER2)
static void clif_authok(struct map_session_data *sd)
{
struct packet_authok p;
nullpo_retv(sd);
p.PacketType = authokType;
p.startTime = (unsigned int)timer->gettick();
WBUFPOS(&p.PosDir[0],0,sd->bl.x,sd->bl.y,sd->ud.dir); /* do the stupid client math */
p.xSize = p.ySize = 5; /* not-used */
#if PACKETVER >= 20080102
p.font = sd->status.font;
#endif
// Some clients smaller than 20160330 cant be tested [4144]
#if PACKETVER >= 20141022 && PACKETVER < 20160330
p.sex = sd->status.sex;
#endif
clif->send(&p,sizeof(p),&sd->bl,SELF);
}
/// [4144] Packet not using error_code anymore. Works for fixed error only (MsgString: 9 - Rejected from Server)
/// Notifies the client, that it's connection attempt was refused (ZC_REFUSE_ENTER).
/// 0074 .B
/// error code:
/// 0 = client type mismatch
/// 1 = ID mismatch
/// 2 = mobile - out of available time
/// 3 = mobile - already logged in
/// 4 = mobile - waiting state
static void clif_authrefuse(int fd, uint8 error_code)
{
WFIFOHEAD(fd,packet_len(0x74));
WFIFOW(fd,0) = 0x74;
WFIFOB(fd,2) = error_code;
WFIFOSET(fd,packet_len(0x74));
}
/// Notifies the client of a ban or forced disconnect (SC_NOTIFY_BAN).
/// 0081 .B
/// error code:
/// 0 = BAN_UNFAIR -> "disconnected from server" -> MsgStringTable[3]
/// 1 = server closed -> MsgStringTable[4]
/// 2 = ID already logged in -> MsgStringTable[5]
/// 3 = timeout/too much lag -> MsgStringTable[241]
/// 4 = server full -> MsgStringTable[264]
/// 5 = underaged -> MsgStringTable[305]
/// 8 = Server sill recognizes last connection -> MsgStringTable[441]
/// 9 = too many connections from this ip -> MsgStringTable[529]
/// 10 = out of available time paid for -> MsgStringTable[530]
/// 11 = BAN_PAY_SUSPEND
/// 12 = BAN_PAY_CHANGE
/// 13 = BAN_PAY_WRONGIP
/// 14 = BAN_PAY_PNGAMEROOM
/// 15 = disconnected by a GM -> if( servicetype == taiwan ) MsgStringTable[579]
/// 16 = BAN_JAPAN_REFUSE1
/// 17 = BAN_JAPAN_REFUSE2
/// 18 = BAN_INFORMATION_REMAINED_ANOTHER_ACCOUNT
/// 100 = BAN_PC_IP_UNFAIR
/// 101 = BAN_PC_IP_COUNT_ALL
/// 102 = BAN_PC_IP_COUNT
/// 103 = BAN_GRAVITY_MEM_AGREE
/// 104 = BAN_GAME_MEM_AGREE
/// 105 = BAN_HAN_VALID
/// 106 = BAN_PC_IP_LIMIT_ACCESS
/// 107 = BAN_OVER_CHARACTER_LIST
/// 108 = BAN_IP_BLOCK
/// 109 = BAN_INVALID_PWD_CNT
/// 110 = BAN_NOT_ALLOWED_JOBCLASS
/// 113 = access is restricted between the hours of midnight to 6:00am.
/// 115 = You are in game connection ban period.
/// ? = disconnected -> MsgStringTable[3]
// TODO: type enum
static void clif_authfail_fd(int fd, int type)
{
if (!fd || !sockt->session[fd] || sockt->session[fd]->func_parse != clif->parse) //clif_authfail should only be invoked on players!
return;
WFIFOHEAD(fd, packet_len(0x81));
WFIFOW(fd,0) = 0x81;
WFIFOB(fd,2) = type;
WFIFOSET(fd,packet_len(0x81));
sockt->eof(fd);
}
/// Notifies the client, whether it can disconnect and change servers (ZC_RESTART_ACK).
/// 00b3 .B
/// type:
/// 1 = disconnect, char-select
/// ? = nothing
static void clif_charselectok(int id, uint8 ok)
{
struct map_session_data* sd;
int fd;
if ((sd = map->id2sd(id)) == NULL || !sd->fd)
return;
fd = sd->fd;
WFIFOHEAD(fd,packet_len(0xb3));
WFIFOW(fd,0) = 0xb3;
WFIFOB(fd,2) = ok;
WFIFOSET(fd,packet_len(0xb3));
}
/// Makes an item appear on the ground.
/// 009e .L .W .B .W .W .B .B .W (ZC_ITEM_FALL_ENTRY)
/// 084b (ZC_ITEM_FALL_ENTRY4)
static void clif_dropflooritem(struct flooritem_data *fitem)
{
struct packet_dropflooritem p;
int view;
nullpo_retv(fitem);
if (fitem->item_data.nameid <= 0)
return;
p.PacketType = dropflooritemType;
p.ITAID = fitem->bl.id;
p.ITID = ((view = itemdb_viewid(fitem->item_data.nameid)) > 0) ? view : fitem->item_data.nameid;
#if PACKETVER >= 20130000 /* not sure date */
p.type = itemtype(itemdb_type(fitem->item_data.nameid));
#endif
p.IsIdentified = fitem->item_data.identify ? 1 : 0;
p.xPos = fitem->bl.x;
p.yPos = fitem->bl.y;
p.subX = fitem->subx;
p.subY = fitem->suby;
p.count = fitem->item_data.amount;
#if defined(PACKETVER_ZERO) || PACKETVER >= 20180418
if (fitem->showdropeffect) {
p.showdropeffect = itemdb_showdropeffect(fitem->item_data.nameid);
p.dropeffectmode = itemdb_dropeffectmode(fitem->item_data.nameid);
} else {
p.showdropeffect = 0;
p.dropeffectmode = 0;
}
#endif
clif->send(&p, sizeof(p), &fitem->bl, AREA);
}
/// Makes an item disappear from the ground.
/// 00a1 .L (ZC_ITEM_DISAPPEAR)
static void clif_clearflooritem(struct flooritem_data *fitem, int fd)
{
unsigned char buf[16];
nullpo_retv(fitem);
WBUFW(buf,0) = 0xa1;
WBUFL(buf,2) = fitem->bl.id;
if (fd == 0) {
clif->send(buf, packet_len(0xa1), &fitem->bl, AREA);
} else {
WFIFOHEAD(fd,packet_len(0xa1));
memcpy(WFIFOP(fd,0), buf, packet_len(0xa1));
WFIFOSET(fd,packet_len(0xa1));
}
}
/// Makes a unit (char, npc, mob, homun) disappear to one client (ZC_NOTIFY_VANISH).
/// 0080 .L .B
/// type:
/// 0 = out of sight
/// 1 = died
/// 2 = logged out
/// 3 = teleport
/// 4 = trickdead
static void clif_clearunit_single(int id, enum clr_type type, int fd)
{
WFIFOHEAD(fd, packet_len(0x80));
WFIFOW(fd,0) = 0x80;
WFIFOL(fd,2) = id;
WFIFOB(fd,6) = type;
WFIFOSET(fd, packet_len(0x80));
}
/// Makes a unit (char, npc, mob, homun) disappear to all clients in area (ZC_NOTIFY_VANISH).
/// 0080 .L .B
/// type:
/// 0 = out of sight
/// 1 = died
/// 2 = logged out
/// 3 = teleport
/// 4 = trickdead
static void clif_clearunit_area(struct block_list *bl, enum clr_type type)
{
unsigned char buf[8];
nullpo_retv(bl);
WBUFW(buf,0) = 0x80;
WBUFL(buf,2) = bl->id;
WBUFB(buf,6) = type;
/**
* When monster dies, there's a delay before the packet is sent,
* so we send it to a bigger area to avoid clients at the edge
* walking out of the area and missing it [KirieZ]
*/
clif->send(buf, packet_len(0x80), bl, type == CLR_DEAD ? AREA_DEAD : AREA_WOS);
if (clif->isdisguised(bl)) {
WBUFL(buf,2) = -bl->id;
clif->send(buf, packet_len(0x80), bl, SELF);
}
}
/// Used to make monsters with player-sprites disappear after dying
/// like normal monsters, because the client does not remove those
/// automatically.
static int clif_clearunit_delayed_sub(int tid, int64 tick, int id, intptr_t data)
{
struct block_list *bl = (struct block_list *)data;
clif->clearunit_area(bl, (enum clr_type) id);
ers_free(clif->delay_clearunit_ers,bl);
return 0;
}
static void clif_clearunit_delayed(struct block_list *bl, enum clr_type type, int64 tick)
{
struct block_list *tbl;
nullpo_retv(bl);
tbl = ers_alloc(clif->delay_clearunit_ers, struct block_list);
memcpy (tbl, bl, sizeof (struct block_list));
timer->add(tick, clif->clearunit_delayed_sub, (int)type, (intptr_t)tbl);
}
/// Gets weapon view info from sd's inventory_data and points (*rhand,*lhand)
static void clif_get_weapon_view(struct map_session_data *sd, int *rhand, int *lhand)
{
nullpo_retv(sd);
nullpo_retv(rhand);
nullpo_retv(lhand);
if(sd->sc.option&OPTION_COSTUME) {
*rhand = *lhand = 0;
return;
}
#if PACKETVER < 4
*rhand = sd->status.look.weapon;
*lhand = sd->status.look.shield;
#else
if (sd->equip_index[EQI_HAND_R] >= 0 &&
sd->inventory_data[sd->equip_index[EQI_HAND_R]])
{
struct item_data* id = sd->inventory_data[sd->equip_index[EQI_HAND_R]];
if (id->view_id > 0)
*rhand = id->view_id;
else
*rhand = id->nameid;
} else
*rhand = 0;
if (sd->equip_index[EQI_HAND_L] >= 0 &&
sd->equip_index[EQI_HAND_L] != sd->equip_index[EQI_HAND_R] &&
sd->inventory_data[sd->equip_index[EQI_HAND_L]])
{
struct item_data* id = sd->inventory_data[sd->equip_index[EQI_HAND_L]];
if (id->view_id > 0)
*lhand = id->view_id;
else
*lhand = id->nameid;
} else
*lhand = 0;
#endif
}
//To make the assignation of the level based on limits clearer/easier. [Skotlex]
static int clif_setlevel_sub(int lv)
{
if( lv < battle_config.max_lv ) {
;
} else if( lv < battle_config.aura_lv ) {
lv = battle_config.max_lv - 1;
} else {
lv = battle_config.max_lv;
}
return lv;
}
static int clif_setlevel(struct block_list *bl)
{
int lv = status->get_lv(bl);
nullpo_retr(0, bl);
if( battle_config.client_limit_unit_lv&bl->type )
return clif_setlevel_sub(lv);
switch( bl->type ) {
case BL_NPC:
case BL_PET:
// npcs and pets do not have level
return 0;
}
return lv;
}
/* for 'packetver < 20091103' 0x78 non-pc-looking unit handling */
static void clif_set_unit_idle2(struct block_list *bl, struct map_session_data *tsd, enum send_target target)
{
#if PACKETVER < 20091103
struct map_session_data* sd;
struct status_change* sc = status->get_sc(bl);
struct view_data* vd = status->get_viewdata(bl);
struct packet_idle_unit2 p;
int g_id = status->get_guild_id(bl);
nullpo_retv(bl);
sd = BL_CAST(BL_PC, bl);
p.PacketType = idle_unit2Type;
#if PACKETVER >= 20071106
p.objecttype = clif->bl_type(bl);
#endif
p.GID = bl->id;
p.speed = status->get_speed(bl);
p.bodyState = (sc) ? sc->opt1 : 0;
p.healthState = (sc) ? sc->opt2 : 0;
p.effectState = (sc != NULL) ? sc->option : ((bl->type == BL_NPC) ? BL_UCCAST(BL_NPC, bl)->option : 0);
p.job = 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 && vd->class == 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 = (sd)? sd->head_dir : 0;
p.GUID = g_id;
p.GEmblemVer = status->get_emblem_id(bl);
p.honor = (sd) ? sd->status.manner : 0;
p.virtue = (sc) ? sc->opt3 : 0;
p.isPKModeON = (sd && sd->status.karma) ? 1 : 0;
p.sex = vd->sex;
WBUFPOS(&p.PosDir[0],0,bl->x,bl->y,unit->getdir(bl));
p.xSize = p.ySize = (sd) ? 5 : 0;
p.state = vd->dead_sit;
p.clevel = clif_setlevel(bl);
clif->send(&p,sizeof(p),tsd?&tsd->bl:bl,target);
#else
return;
#endif
}
/*==========================================
* Prepares 'unit standing' packet
*------------------------------------------*/
static void clif_set_unit_idle(struct block_list *bl, struct map_session_data *tsd, enum send_target target)
{
struct map_session_data* sd;
struct status_change* sc = status->get_sc(bl);
struct view_data* vd = status->get_viewdata(bl);
struct packet_idle_unit p;
int g_id = status->get_guild_id(bl);
nullpo_retv(bl);
#if PACKETVER < 20091103
if (!pc->db_checkid(vd->class)) {
clif->set_unit_idle2(bl,tsd,target);
return;
}
#endif
sd = BL_CAST(BL_PC, bl);
p.PacketType = idle_unitType;
#if PACKETVER >= 20091103
p.PacketLength = sizeof(p);
p.objecttype = clif->bl_type(bl);
#endif
#if PACKETVER >= 20131223
p.AID = bl->id;
p.GID = (sd) ? sd->status.char_id : 0; // CCODE
#else
p.GID = bl->id;
#endif
p.speed = status->get_speed(bl);
p.bodyState = (sc) ? sc->opt1 : 0;
p.healthState = (sc) ? sc->opt2 : 0;
p.effectState = (sc != NULL) ? sc->option : ((bl->type == BL_NPC) ? BL_UCCAST(BL_NPC, bl)->option : 0);
p.job = vd->class;
p.head = vd->hair_style;
p.weapon = vd->weapon;
p.accessory = vd->head_bottom;
#if PACKETVER < 7 || PACKETVER_MAIN_NUM >= 20181121 || PACKETVER_RE_NUM >= 20180704 || PACKETVER_ZERO_NUM >= 20181114
p.shield = vd->shield;
#endif
p.accessory2 = vd->head_top;
p.accessory3 = vd->head_mid;
if (bl->type == BL_NPC && vd->class == 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 = (sd)? sd->head_dir : 0;
#if PACKETVER >= 20101124
p.robe = vd->robe;
#endif
p.GUID = g_id;
p.GEmblemVer = status->get_emblem_id(bl);
p.honor = (sd) ? sd->status.manner : 0;
p.virtue = (sc) ? sc->opt3 : 0;
p.isPKModeON = (sd && sd->status.karma) ? 1 : 0;
p.sex = vd->sex;
WBUFPOS(&p.PosDir[0],0,bl->x,bl->y,unit->getdir(bl));
p.xSize = p.ySize = (sd) ? 5 : 0;
p.state = vd->dead_sit;
p.clevel = clif_setlevel(bl);
#if PACKETVER >= 20080102
p.font = (sd) ? sd->status.font : 0;
#endif
#if PACKETVER >= 20120221
if (battle_config.show_monster_hp_bar && bl->type == BL_MOB && status_get_hp(bl) < status_get_max_hp(bl)) {
p.maxHP = status_get_max_hp(bl);
p.HP = status_get_hp(bl);
} else {
p.maxHP = -1;
p.HP = -1;
}
if (bl->type == BL_MOB) {
const struct mob_data *md = BL_UCCAST(BL_MOB, bl);
p.isBoss = (md->spawn != NULL) ? md->spawn->state.boss : BTYPE_NONE;
} else {
p.isBoss = BTYPE_NONE;
}
#endif
#if PACKETVER >= 20150513
p.body = vd->body_style;
#endif
/* Might be earlier, this is when the named item bug began */
#if PACKETVER >= 20131223
safestrncpy(p.name, clif->get_bl_name(bl), NAME_LENGTH);
#endif
clif->send(&p,sizeof(p),tsd?&tsd->bl:bl,target);
if (clif->isdisguised(bl)) {
#if PACKETVER >= 20091103
p.objecttype = pc->db_checkid(status->get_viewdata(bl)->class) ? 0x0 : 0x5; //PC_TYPE : NPC_MOB_TYPE
#if PACKETVER >= 20131223
p.AID = -bl->id;
#else
p.GID = -bl->id;
#endif
#else
p.GID = -bl->id;
#endif
clif->send(&p,sizeof(p),bl,SELF);
}
}
/* for 'packetver < 20091103' 0x7c non-pc-looking unit handling */
static void clif_spawn_unit2(struct block_list *bl, enum send_target target)
{
#if PACKETVER < 20091103
struct map_session_data* sd;
struct status_change* sc = status->get_sc(bl);
struct view_data* vd = status->get_viewdata(bl);
struct packet_spawn_unit2 p;
int g_id = status->get_guild_id(bl);
nullpo_retv(bl);
sd = BL_CAST(BL_PC, bl);
p.PacketType = spawn_unit2Type;
#if PACKETVER >= 20071106
p.objecttype = clif->bl_type(bl);
#endif
p.GID = bl->id;
p.speed = status->get_speed(bl);
p.bodyState = (sc) ? sc->opt1 : 0;
p.healthState = (sc) ? sc->opt2 : 0;
p.effectState = (sc != NULL) ? sc->option : ((bl->type == BL_NPC) ? BL_UCCAST(BL_NPC, bl)->option : 0);
p.head = vd->hair_style;
p.weapon = vd->weapon;
p.accessory = vd->head_bottom;
p.job = vd->class;
p.shield = vd->shield;
p.accessory2 = vd->head_top;
p.accessory3 = vd->head_mid;
if (bl->type == BL_NPC && vd->class == 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 = (sd)? sd->head_dir : 0;
p.isPKModeON = (sd && sd->status.karma) ? 1 : 0;
p.sex = vd->sex;
WBUFPOS(&p.PosDir[0],0,bl->x,bl->y,unit->getdir(bl));
p.xSize = p.ySize = (sd) ? 5 : 0;
clif->send(&p,sizeof(p),bl,target);
#else
return;
#endif
}
static void clif_spawn_unit(struct block_list *bl, enum send_target target)
{
struct map_session_data* sd;
struct status_change* sc = status->get_sc(bl);
struct view_data* vd = status->get_viewdata(bl);
struct packet_spawn_unit p;
int g_id = status->get_guild_id(bl);
nullpo_retv(bl);
#if PACKETVER < 20091103
if (!pc->db_checkid(vd->class)) {
clif->spawn_unit2(bl,target);
return;
}
#endif
sd = BL_CAST(BL_PC, bl);
p.PacketType = spawn_unitType;
#if PACKETVER >= 20091103
p.PacketLength = sizeof(p);
p.objecttype = clif->bl_type(bl);
#endif
#if PACKETVER >= 20131223
p.AID = bl->id;
p.GID = (sd) ? sd->status.char_id : 0; // CCODE
#else
p.GID = bl->id;
#endif
p.speed = status->get_speed(bl);
p.bodyState = (sc) ? sc->opt1 : 0;
p.healthState = (sc) ? sc->opt2 : 0;
p.effectState = (sc != NULL) ? sc->option : ((bl->type == BL_NPC) ? BL_UCCAST(BL_NPC, bl)->option : 0);
p.job = vd->class;
p.head = vd->hair_style;
p.weapon = vd->weapon;
p.accessory = vd->head_bottom;
#if PACKETVER < 7 || PACKETVER_MAIN_NUM >= 20181121 || PACKETVER_RE_NUM >= 20180704 || PACKETVER_ZERO_NUM >= 20181114
p.shield = vd->shield;
#endif
p.accessory2 = vd->head_top;
p.accessory3 = vd->head_mid;
if (bl->type == BL_NPC && vd->class == 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 = (sd)? sd->head_dir : 0;
#if PACKETVER >= 20101124
p.robe = vd->robe;
#endif
p.GUID = g_id;
p.GEmblemVer = status->get_emblem_id(bl);
p.honor = (sd) ? sd->status.manner : 0;
p.virtue = (sc) ? sc->opt3 : 0;
p.isPKModeON = (sd && sd->status.karma) ? 1 : 0;
p.sex = vd->sex;
WBUFPOS(&p.PosDir[0],0,bl->x,bl->y,unit->getdir(bl));
p.xSize = p.ySize = (sd) ? 5 : 0;
p.clevel = clif_setlevel(bl);
#if PACKETVER >= 20080102
p.font = (sd) ? sd->status.font : 0;
#endif
#if PACKETVER >= 20120221
if (battle_config.show_monster_hp_bar && bl->type == BL_MOB && status_get_hp(bl) < status_get_max_hp(bl)) {
p.maxHP = status_get_max_hp(bl);
p.HP = status_get_hp(bl);
} else {
p.maxHP = -1;
p.HP = -1;
}
if (bl->type == BL_MOB) {
const struct mob_data *md = BL_UCCAST(BL_MOB, bl);
p.isBoss = (md->spawn != NULL) ? md->spawn->state.boss : BTYPE_NONE;
} else {
p.isBoss = BTYPE_NONE;
}
#endif
#if PACKETVER >= 20150513
p.body = vd->body_style;
#endif
/* Might be earlier, this is when the named item bug began */
#if PACKETVER >= 20131223
safestrncpy(p.name, clif->get_bl_name(bl), NAME_LENGTH);
#endif
if (clif->isdisguised(bl)) {
nullpo_retv(sd);
if (sd->status.class != sd->disguise)
clif->send(&p,sizeof(p),bl,target);
#if PACKETVER >= 20091103
p.objecttype = pc->db_checkid(status->get_viewdata(bl)->class) ? 0x0 : 0x5; //PC_TYPE : NPC_MOB_TYPE
#if PACKETVER >= 20131223
p.AID = -bl->id;
#else
p.GID = -bl->id;
#endif
#else
p.GID = -bl->id;
#endif
clif->send(&p,sizeof(p),bl,SELF);
} else
clif->send(&p,sizeof(p),bl,target);
}
/*==========================================
* Prepares 'unit walking' packet
*------------------------------------------*/
static void clif_set_unit_walking(struct block_list *bl, struct map_session_data *tsd, struct unit_data *ud, enum send_target target)
{
struct map_session_data* sd;
struct status_change* sc = status->get_sc(bl);
struct view_data* vd = status->get_viewdata(bl);
struct packet_unit_walking p;
int g_id = status->get_guild_id(bl);
nullpo_retv(bl);
nullpo_retv(ud);
sd = BL_CAST(BL_PC, bl);
p.PacketType = unit_walkingType;
#if PACKETVER >= 20091103
p.PacketLength = sizeof(p);
#endif
#if PACKETVER >= 20071106
p.objecttype = clif->bl_type(bl);
#endif
#if PACKETVER >= 20131223
p.AID = bl->id;
p.GID = (sd) ? sd->status.char_id : 0; // CCODE
#else
p.GID = bl->id;
#endif
p.speed = status->get_speed(bl);
p.bodyState = (sc) ? sc->opt1 : 0;
p.healthState = (sc) ? sc->opt2 : 0;
p.effectState = (sc != NULL) ? sc->option : ((bl->type == BL_NPC) ? BL_UCCAST(BL_NPC, bl)->option : 0);
p.job = vd->class;
p.head = vd->hair_style;
p.weapon = vd->weapon;
p.accessory = vd->head_bottom;
p.moveStartTime = (unsigned int)timer->gettick();
#if PACKETVER < 7 || PACKETVER_MAIN_NUM >= 20181121 || PACKETVER_RE_NUM >= 20180704 || PACKETVER_ZERO_NUM >= 20181114
p.shield = vd->shield;
#endif
p.accessory2 = vd->head_top;
p.accessory3 = vd->head_mid;
p.headpalette = vd->hair_color;
p.bodypalette = vd->cloth_color;
p.headDir = (sd)? sd->head_dir : 0;
#if PACKETVER >= 20101124
p.robe = vd->robe;
#endif
p.GUID = g_id;
p.GEmblemVer = status->get_emblem_id(bl);
p.honor = (sd) ? sd->status.manner : 0;
p.virtue = (sc) ? sc->opt3 : 0;
p.isPKModeON = (sd && sd->status.karma) ? 1 : 0;
p.sex = vd->sex;
WBUFPOS2(&p.MoveData[0],0,bl->x,bl->y,ud->to_x,ud->to_y,8,8);
p.xSize = p.ySize = (sd) ? 5 : 0;
p.clevel = clif_setlevel(bl);
#if PACKETVER >= 20080102
p.font = (sd) ? sd->status.font : 0;
#endif
#if PACKETVER >= 20120221
if (battle_config.show_monster_hp_bar && bl->type == BL_MOB && status_get_hp(bl) < status_get_max_hp(bl)) {
p.maxHP = status_get_max_hp(bl);
p.HP = status_get_hp(bl);
} else {
p.maxHP = -1;
p.HP = -1;
}
if (bl->type == BL_MOB) {
const struct mob_data *md = BL_UCCAST(BL_MOB, bl);
p.isBoss = (md->spawn != NULL) ? md->spawn->state.boss : BTYPE_NONE;
} else {
p.isBoss = BTYPE_NONE;
}
#endif
#if PACKETVER >= 20150513
p.body = vd->body_style;
#endif
/* Might be earlier, this is when the named item bug began */
#if PACKETVER >= 20131223
safestrncpy(p.name, clif->get_bl_name(bl), NAME_LENGTH);
#endif
clif->send(&p,sizeof(p),tsd?&tsd->bl:bl,target);
if (clif->isdisguised(bl)) {
#if PACKETVER >= 20091103
p.objecttype = pc->db_checkid(status->get_viewdata(bl)->class) ? 0x0 : 0x5; //PC_TYPE : NPC_MOB_TYPE
#if PACKETVER >= 20131223
p.AID = -bl->id;
#else
p.GID = -bl->id;
#endif
#else
p.GID = -bl->id;
#endif
clif->send(&p,sizeof(p),bl,SELF);
}
}
/// Changes sprite of an NPC object (ZC_NPCSPRITE_CHANGE).
/// 01b0 .L .B .L
/// type:
/// unused
static void clif_class_change(struct block_list *bl, int class_, int type, struct map_session_data *sd)
{
nullpo_retv(bl);
if(!pc->db_checkid(class_))
{// player classes yield missing sprites
unsigned char buf[16];
WBUFW(buf,0)=0x1b0;
WBUFL(buf,2)=bl->id;
WBUFB(buf,6)=type;
WBUFL(buf,7)=class_;
if (sd == NULL)
clif->send(buf, packet_len(0x1b0), bl, AREA);
else
clif->send(buf, packet_len(0x1b0), &sd->bl, SELF);
}
}
/// Notifies the client of an object's spirits.
/// 01d0 .L .W (ZC_SPIRITS)
/// 01e1 .L .W (ZC_SPIRITS2)
static void clif_spiritball_single(int fd, struct map_session_data *sd)
{
nullpo_retv(sd);
WFIFOHEAD(fd, packet_len(0x1e1));
WFIFOW(fd,0)=0x1e1;
WFIFOL(fd,2)=sd->bl.id;
WFIFOW(fd,6)=sd->spiritball;
WFIFOSET(fd, packet_len(0x1e1));
}
/*==========================================
* Kagerou/Oboro amulet spirit
*------------------------------------------*/
static void clif_charm_single(int fd, struct map_session_data *sd)
{
#if PACKETVER >= 20110809
nullpo_retv(sd);
WFIFOHEAD(fd, packet_len(0x08cf));
WFIFOW(fd,0) = 0x08cf;
WFIFOL(fd,2) = sd->bl.id;
WFIFOW(fd,6) = sd->charm_type;
WFIFOW(fd,8) = sd->charm_count;
WFIFOSET(fd, packet_len(0x08cf));
#endif
}
/*==========================================
* Run when player changes map / refreshes
* Tells its client to display all weather settings being used by this map
*------------------------------------------*/
static void clif_weather_check(struct map_session_data *sd)
{
int16 m;
int fd;
nullpo_retv(sd);
m = sd->bl.m;
fd = sd->fd;
if (map->list[m].flag.snow)
clif->specialeffect_single(&sd->bl, 162, fd);
if (map->list[m].flag.clouds)
clif->specialeffect_single(&sd->bl, 233, fd);
if (map->list[m].flag.clouds2)
clif->specialeffect_single(&sd->bl, 516, fd);
if (map->list[m].flag.fog)
clif->specialeffect_single(&sd->bl, 515, fd);
if (map->list[m].flag.fireworks) {
clif->specialeffect_single(&sd->bl, 297, fd);
clif->specialeffect_single(&sd->bl, 299, fd);
clif->specialeffect_single(&sd->bl, 301, fd);
}
if (map->list[m].flag.sakura)
clif->specialeffect_single(&sd->bl, 163, fd);
if (map->list[m].flag.leaves)
clif->specialeffect_single(&sd->bl, 333, fd);
}
/**
* Run when the weather on a map changes, throws all players in map id 'm' to clif_weather_check function
**/
static void clif_weather(int16 m)
{
struct s_mapiterator* iter;
struct map_session_data *sd=NULL;
iter = mapit_getallusers();
for (sd = BL_UCAST(BL_PC, mapit->first(iter)); mapit->exists(iter); sd = BL_UCAST(BL_PC, mapit->next(iter))) {
if( sd->bl.m == m )
clif->weather_check(sd);
}
mapit->free(iter);
}
/**
* Main function to spawn a unit on the client (player/mob/pet/etc)
**/
static bool clif_spawn(struct block_list *bl)
{
struct view_data *vd;
nullpo_retr(false, bl);
vd = status->get_viewdata(bl);
if( !vd )
return false;
if (vd->class == INVISIBLE_CLASS)
return true; // Doesn't need to be spawned, so everything is alright
if (bl->type == BL_NPC) {
struct npc_data *nd = BL_UCAST(BL_NPC, bl);
if (nd->chat_id == 0 && (nd->option&OPTION_INVISIBLE)) // Hide NPC from maya purple card.
return true; // Doesn't need to be spawned, so everything is alright
}
clif->spawn_unit(bl,AREA_WOS);
if (vd->cloth_color)
clif->refreshlook(bl,bl->id,LOOK_CLOTHES_COLOR,vd->cloth_color,AREA_WOS);
if (vd->body_style)
clif->refreshlook(bl,bl->id,LOOK_BODY2,vd->body_style,AREA_WOS);
switch (bl->type) {
case BL_PC:
{
struct map_session_data *sd = BL_UCAST(BL_PC, bl);
int i;
if (sd->spiritball > 0)
clif->spiritball(&sd->bl);
if (sd->state.size == SZ_BIG) // tiny/big players [Valaris]
clif->specialeffect(bl,423,AREA);
else if (sd->state.size == SZ_MEDIUM)
clif->specialeffect(bl,421,AREA);
if (sd->bg_id != 0 && map->list[sd->bl.m].flag.battleground)
clif->sendbgemblem_area(sd);
for (i = 0; i < sd->sc_display_count; i++) {
clif->sc_continue(&sd->bl, sd->bl.id, AREA, status->get_sc_icon(sd->sc_display[i]->type), sd->sc_display[i]->val1, sd->sc_display[i]->val2, sd->sc_display[i]->val3);
}
if (sd->charm_type != CHARM_TYPE_NONE && sd->charm_count > 0)
clif->spiritcharm(sd);
if (sd->status.look.robe != 0)
clif->refreshlook(bl, bl->id, LOOK_ROBE, sd->status.look.robe, AREA);
clif->hat_effect(bl, NULL, AREA);
}
break;
case BL_MOB:
{
struct mob_data *md = BL_UCAST(BL_MOB, bl);
if (md->special_state.size==SZ_BIG) // tiny/big mobs [Valaris]
clif->specialeffect(&md->bl,423,AREA);
else if (md->special_state.size==SZ_MEDIUM)
clif->specialeffect(&md->bl,421,AREA);
}
break;
case BL_NPC:
{
struct npc_data *nd = BL_UCAST(BL_NPC, bl);
if (nd->size == SZ_BIG)
clif->specialeffect(&nd->bl,423,AREA);
else if (nd->size == SZ_MEDIUM)
clif->specialeffect(&nd->bl,421,AREA);
if (nd->clan_id > 0)
clif->sc_load(&nd->bl, nd->bl.id, AREA, status->get_sc_icon(SC_CLAN_INFO), 0, nd->clan_id, 0);
}
break;
case BL_PET:
if (vd->head_bottom)
clif->send_petdata(NULL, BL_UCAST(BL_PET, bl), 3, vd->head_bottom); // needed to display pet equip properly
break;
}
return true;
}
/// Sends information about owned homunculus to the client (ZC_PROPERTY_HOMUN). [orn]
/// 022e .24B .B .W .W .W .W .W .W .W .W .W .W .W .W .W .W .W .W .L .L .W .W
static void clif_hominfo(struct map_session_data *sd, struct homun_data *hd, int flag)
{
#if PACKETVER_MAIN_NUM >= 20101005 || PACKETVER_RE_NUM >= 20080827 || defined(PACKETVER_ZERO)
struct status_data *hstatus;
enum homun_type htype;
struct PACKET_ZC_PROPERTY_HOMUN p;
nullpo_retv(sd);
nullpo_retv(hd);
hstatus = &hd->battle_status;
htype = homun->class2type(hd->homunculus.class_);
memset(&p, 0, sizeof(p));
p.packetType = HEADER_ZC_PROPERTY_HOMUN;
memcpy(p.name, hd->homunculus.name, NAME_LENGTH);
// Bit field, bit 0 : rename_flag (1 = already renamed), bit 1 : homunc vaporized (1 = true), bit 2 : homunc dead (1 = true)
p.flags = (!battle_config.hom_rename && hd->homunculus.rename_flag ? 0x1 : 0x0) | (hd->homunculus.vaporize == HOM_ST_REST ? 0x2 : 0) | (hd->homunculus.hp > 0 ? 0x4 : 0);
p.level = hd->homunculus.level;
p.hunger = hd->homunculus.hunger;
p.intimacy = hd->homunculus.intimacy / 100;
#if !(PACKETVER_MAIN_NUM >= 20190619 || PACKETVER_RE_NUM >= 20190605 || PACKETVER_ZERO_NUM >= 20190626)
p.itemId = 0; // equip id
#endif
#ifdef RENEWAL
p.atk2 = cap_value(hstatus->rhw.atk2, 0, INT16_MAX);
#else
p.atk2 = cap_value(hstatus->rhw.atk2 + hstatus->batk, 0, INT16_MAX);
#endif
p.matk = cap_value(hstatus->matk_max, 0, INT16_MAX);
p.hit = hstatus->hit;
if (battle_config.hom_setting&0x10)
p.crit = hstatus->luk / 3 + 1; //crit is a +1 decimal value! Just display purpose.[Vicious]
else
p.crit = hstatus->cri / 10;
#ifdef RENEWAL
p.def = hstatus->def + hstatus->def2;
p.mdef = hstatus->mdef + hstatus->mdef2;
#else
p.def = hstatus->def + hstatus->vit ;
p.mdef = hstatus->mdef;
#endif
p.flee = hstatus->flee;
p.amotion = (flag) ? 0 : hstatus->amotion;
// probably can works also for < 20141223, but in 3CeaM packet size defined only for 20150513
#if PACKETVER < 20150513
if (hstatus->max_hp > INT16_MAX) {
p.hp = hstatus->hp / (hstatus->max_hp / 100);
p.maxHp = 100;
} else {
p.hp = hstatus->hp;
p.maxHp = hstatus->max_hp;
}
#else
p.hp = hstatus->hp;
p.maxHp = hstatus->max_hp;
#endif
if (hstatus->max_sp > INT16_MAX) {
p.sp = hstatus->sp / (hstatus->max_sp / 100);
p.maxSp = 100;
} else {
p.sp = hstatus->sp;
p.maxSp = hstatus->max_sp;
}
p.exp = hd->homunculus.exp;
p.expNext = hd->exp_next;
switch (htype) {
case HT_REG:
case HT_EVO:
if (hd->homunculus.level >= battle_config.hom_max_level)
p.expNext = 0;
break;
case HT_S:
if (hd->homunculus.level >= battle_config.hom_S_max_level)
p.expNext = 0;
break;
}
p.skillPoints = hd->homunculus.skillpts;
p.range = status_get_range(&hd->bl);
clif->send(&p, sizeof(p), &sd->bl, SELF);
#endif
}
/// Notification about a change in homunuculus' state (ZC_CHANGESTATE_MER).
/// 0230 .B .B .L .L
/// type:
/// unused
/// state:
/// 0 = pre-init
/// 1 = intimacy
/// 2 = hunger
/// 3 = accessory?
/// ? = ignored
static void clif_send_homdata(struct map_session_data *sd, int state, int param)
{
int fd;
nullpo_retv(sd);
nullpo_retv(sd->hd);
fd = sd->fd;
if ( (state == SP_INTIMATE) && (param >= 910) && (sd->hd->homunculus.class_ == sd->hd->homunculusDB->evo_class) )
homun->calc_skilltree(sd->hd, 0);
WFIFOHEAD(fd, packet_len(0x230));
WFIFOW(fd,0)=0x230;
WFIFOB(fd,2)=0;
WFIFOB(fd,3)=state;
WFIFOL(fd,4)=sd->hd->bl.id;
WFIFOL(fd,8)=param;
WFIFOSET(fd,packet_len(0x230));
}
/// Prepares and sends homun related information [orn]
static void clif_homskillinfoblock(struct map_session_data *sd)
{
struct homun_data *hd;
int fd;
int i,j;
int len=4;
nullpo_retv(sd);
fd = sd->fd;
hd = sd->hd;
if ( !hd )
return;
WFIFOHEAD(fd, 4+37*MAX_HOMUNSKILL);
WFIFOW(fd,0)=0x235;
for ( i = 0; i < MAX_HOMUNSKILL; i++ ) {
int id = hd->homunculus.hskill[i].id;
if ( id != 0 ) {
j = id - HM_SKILLBASE;
WFIFOW(fd, len) = id;
WFIFOL(fd, len + 2) = skill->get_inf(id);
WFIFOW(fd, len + 6) = hd->homunculus.hskill[j].lv;
if ( hd->homunculus.hskill[j].lv ) {
WFIFOW(fd, len + 8) = skill->get_sp(id, hd->homunculus.hskill[j].lv);
WFIFOW(fd, len + 10) = skill->get_range2(&sd->hd->bl, id, hd->homunculus.hskill[j].lv);
} else {
WFIFOW(fd, len + 8) = 0;
WFIFOW(fd, len + 10) = 0;
}
safestrncpy(WFIFOP(fd, len + 12), skill->get_name(id), NAME_LENGTH);
WFIFOB(fd, len + 36) = (hd->homunculus.hskill[j].lv < homun->skill_tree_get_max(id, hd->homunculus.class_)) ? 1 : 0;
len += 37;
}
}
WFIFOW(fd,2)=len;
WFIFOSET(fd,len);
return;
}
//[orn]
static void clif_homskillup(struct map_session_data *sd, uint16 skill_id)
{
struct homun_data *hd;
int fd, idx;
nullpo_retv(sd);
nullpo_retv(sd->hd);
idx = skill_id - HM_SKILLBASE;
fd=sd->fd;
hd=sd->hd;
WFIFOHEAD(fd, packet_len(0x239));
WFIFOW(fd,0) = 0x239;
WFIFOW(fd,2) = skill_id;
WFIFOW(fd,4) = hd->homunculus.hskill[idx].lv;
WFIFOW(fd,6) = skill->get_sp(skill_id,hd->homunculus.hskill[idx].lv);
WFIFOW(fd,8) = skill->get_range2(&hd->bl, skill_id,hd->homunculus.hskill[idx].lv);
WFIFOB(fd,10) = (hd->homunculus.hskill[idx].lv < skill->get_max(hd->homunculus.hskill[idx].id)) ? 1 : 0;
WFIFOSET(fd,packet_len(0x239));
}
/// Result of request to feed a homun/merc (ZC_FEED_MER).
/// 022f .B .W
/// result:
/// 0 = failure
/// 1 = success
static void clif_hom_food(struct map_session_data *sd, int foodid, int fail)
{
int fd;
struct PACKET_ZC_FEED_MER p;
nullpo_retv(sd);
fd = sd->fd;
WFIFOHEAD(fd, sizeof(p));
p.packetType = 0x22f;
p.result = fail;
p.itemId = foodid;
memcpy(WFIFOP(fd, 0), &p, sizeof(p));
WFIFOSET(fd, sizeof(p));
}
/// Notifies the client, that it is walking (ZC_NOTIFY_PLAYERMOVE).
/// 0087 .L .6B
static void clif_walkok(struct map_session_data *sd)
{
int fd;
nullpo_retv(sd);
fd = sd->fd;
WFIFOHEAD(fd, packet_len(0x87));
WFIFOW(fd,0)=0x87;
WFIFOL(fd,2)=(unsigned int)timer->gettick();
WFIFOPOS2(fd,6,sd->bl.x,sd->bl.y,sd->ud.to_x,sd->ud.to_y,8,8);
WFIFOSET(fd,packet_len(0x87));
}
static void clif_move2(struct block_list *bl, struct view_data *vd, struct unit_data *ud)
{
#ifdef ANTI_MAYAP_CHEAT
struct status_change *sc = NULL;
#endif
nullpo_retv(bl);
nullpo_retv(vd);
nullpo_retv(ud);
#ifdef ANTI_MAYAP_CHEAT
if( (sc = status->get_sc(bl)) && sc->option&(OPTION_HIDE|OPTION_CLOAK|OPTION_INVISIBLE|OPTION_CHASEWALK) )
clif->ally_only = true;
#endif
clif->set_unit_walking(bl,NULL,ud,AREA_WOS);
if(vd->cloth_color)
clif->refreshlook(bl,bl->id,LOOK_CLOTHES_COLOR,vd->cloth_color,AREA_WOS);
if (vd->body_style)
clif->refreshlook(bl,bl->id,LOOK_BODY2,vd->body_style,AREA_WOS);
switch(bl->type) {
case BL_PC:
{
struct map_session_data *sd = BL_UCAST(BL_PC, bl);
//clif_movepc(sd);
if(sd->state.size==SZ_BIG) // tiny/big players [Valaris]
clif->specialeffect(&sd->bl,423,AREA);
else if(sd->state.size==SZ_MEDIUM)
clif->specialeffect(&sd->bl,421,AREA);
}
break;
case BL_MOB:
{
struct mob_data *md = BL_UCAST(BL_MOB, bl);
if (md->special_state.size == SZ_BIG) // tiny/big mobs [Valaris]
clif->specialeffect(&md->bl,423,AREA);
else if (md->special_state.size == SZ_MEDIUM)
clif->specialeffect(&md->bl,421,AREA);
}
break;
case BL_PET:
if( vd->head_bottom ) // needed to display pet equip properly
clif->send_petdata(NULL, BL_UCAST(BL_PET, bl), 3, vd->head_bottom);
break;
}
#ifdef ANTI_MAYAP_CHEAT
clif->ally_only = false;
#endif
}
/// Notifies clients in an area, that an other visible object is walking (ZC_NOTIFY_PLAYERMOVE).
/// 0086 .L .6B .L
/// Note: unit must not be self
static void clif_move(struct unit_data *ud)
{
unsigned char buf[16];
struct view_data *vd;
struct block_list *bl;
#ifdef ANTI_MAYAP_CHEAT
struct status_change *sc = NULL;
#endif
nullpo_retv(ud);
bl = ud->bl;
nullpo_retv(bl);
vd = status->get_viewdata(bl);
if (vd == NULL || vd->class == INVISIBLE_CLASS)
return; //This performance check is needed to keep GM-hidden objects from being notified to bots.
if (bl->type == BL_NPC) {
// Hide NPC from maya purple card.
struct npc_data *nd = BL_UCAST(BL_NPC, bl);
if (nd->chat_id == 0 && (nd->option&OPTION_INVISIBLE))
return;
}
if (ud->state.speed_changed) {
// Since we don't know how to update the speed of other objects,
// use the old walk packet to update the data.
ud->state.speed_changed = 0;
clif->move2(bl, vd, ud);
return;
}
#ifdef ANTI_MAYAP_CHEAT
if( (sc = status->get_sc(bl)) && sc->option&(OPTION_HIDE|OPTION_CLOAK|OPTION_INVISIBLE) )
clif->ally_only = true;
#endif
WBUFW(buf,0)=0x86;
WBUFL(buf,2)=bl->id;
WBUFPOS2(buf,6,bl->x,bl->y,ud->to_x,ud->to_y,8,8);
WBUFL(buf,12)=(unsigned int)timer->gettick();
clif->send(buf, packet_len(0x86), bl, AREA_WOS);
if (clif->isdisguised(bl)) {
WBUFL(buf,2)=-bl->id;
clif->send(buf, packet_len(0x86), bl, SELF);
}
#ifdef ANTI_MAYAP_CHEAT
clif->ally_only = false;
#endif
}
/*==========================================
* Delays the map->quit of a player after they are disconnected. [Skotlex]
*------------------------------------------*/
static int clif_delayquit(int tid, int64 tick, int id, intptr_t data)
{
struct map_session_data *sd = NULL;
//Remove player from map server
if ((sd = map->id2sd(id)) != NULL && sd->fd == 0) //Should be a disconnected player.
map->quit(sd);
return 0;
}
/*==========================================
*
*------------------------------------------*/
static void clif_quitsave(int fd, struct map_session_data *sd)
{
nullpo_retv(sd);
if (!battle_config.prevent_logout ||
DIFF_TICK(timer->gettick(), sd->canlog_tick) > battle_config.prevent_logout)
map->quit(sd);
else if (sd->fd) {
//Disassociate session from player (session is deleted after this function was called)
//And set a timer to make him quit later.
sockt->session[sd->fd]->session_data = NULL;
sd->fd = 0;
timer->add(timer->gettick() + 10000, clif->delayquit, sd->bl.id, 0);
}
}
/// Notifies the client of a position change to coordinates on given map (ZC_NPCACK_MAPMOVE).
/// 0091