// Copyright (c) rAthena Dev Teams - Licensed under GNU GPL // Copyright (c) Hercules Dev Team - Licensed under GNU GPL // For more information, see LICENCE in the main folder #include "clif.hpp" #include #include #include #include #include #include "../common/cbasetypes.hpp" #include "../common/conf.hpp" #include "../common/ers.hpp" #include "../common/grfio.hpp" #include "../common/malloc.hpp" #include "../common/nullpo.hpp" #include "../common/random.hpp" #include "../common/showmsg.hpp" #include "../common/socket.hpp" #include "../common/strlib.hpp" #include "../common/timer.hpp" #include "../common/utilities.hpp" #include "../common/utils.hpp" #include "achievement.hpp" #include "atcommand.hpp" #include "battle.hpp" #include "battleground.hpp" #include "cashshop.hpp" #include "channel.hpp" #include "chat.hpp" #include "chrif.hpp" #include "clan.hpp" #include "clif.hpp" #include "elemental.hpp" #include "guild.hpp" #include "homunculus.hpp" #include "instance.hpp" #include "intif.hpp" #include "itemdb.hpp" #include "log.hpp" #include "mail.hpp" #include "map.hpp" #include "mercenary.hpp" #include "mob.hpp" #include "npc.hpp" #include "party.hpp" #include "pc.hpp" #include "pc_groups.hpp" #include "pet.hpp" #include "quest.hpp" #include "script.hpp" #include "skill.hpp" #include "status.hpp" #include "storage.hpp" #include "trade.hpp" #include "unit.hpp" #include "vending.hpp" using namespace rathena; static inline uint32 client_tick( t_tick tick ){ return (uint32)tick; } #if PACKETVER >= 20170830 static inline int64 client_exp(t_exp exp) { return (int64)u64min(exp, MAX_EXP); } #else static inline int32 client_exp(t_exp exp) { return (int32)u64min(exp, MAX_EXP); } #endif /* for clif_clearunit_delayed */ static struct eri *delay_clearunit_ers; struct s_packet_db packet_db[MAX_PACKET_DB + 1]; int packet_db_ack[MAX_ACK_FUNC + 1]; // Reuseable global packet buffer to prevent too many allocations // Take socket.cpp::socket_max_client_packet into consideration static int8 packet_buffer[UINT16_MAX]; unsigned long color_table[COLOR_MAX]; #include "clif_obfuscation.hpp" static bool clif_session_isValid(struct map_session_data *sd); static void clif_loadConfirm( struct map_session_data *sd ); #if PACKETVER >= 20150513 enum mail_type { MAIL_TYPE_TEXT = 0x0, MAIL_TYPE_ZENY = 0x2, MAIL_TYPE_ITEM = 0x4, MAIL_TYPE_NPC = 0x8 }; #endif enum e_inventory_type{ INVTYPE_INVENTORY = 0, INVTYPE_CART = 1, INVTYPE_STORAGE = 2, INVTYPE_GUILD_STORAGE = 3, }; /** Converts item type to display it on client if necessary. * @param nameid: Item ID * @return item type. For IT_PETEGG will be displayed as IT_ARMOR. If Shadow Weapon of IT_SHADOWGEAR as IT_WEAPON and else as IT_ARMOR */ static inline int itemtype(t_itemid nameid) { struct item_data* id = itemdb_search(nameid); //Use itemdb_search, so non-existance item will use dummy data and won't crash the server. bugreport:8468 int type = id->type; if( type == IT_SHADOWGEAR ) { if( id->equip&EQP_SHADOW_WEAPON ) return IT_WEAPON; else return IT_ARMOR; } return ( type == IT_PETEGG ) ? IT_ARMOR : type; } // TODO: doc static inline uint16 client_index( uint16 server_index ){ return server_index + 2; } static inline uint16 server_index( uint16 client_index ){ return client_index - 2; } static inline uint16 client_storage_index( uint16 server_index ){ return server_index + 1; } static inline uint16 server_storage_index( uint16 client_index ){ return client_index - 1; } static inline uint32 disguised_bl_id( uint32 bl_id ){ // Casting to prevent a compiler warning return -((int32)bl_id); } #if PACKETVER_MAIN_NUM >= 20181121 || PACKETVER_RE_NUM >= 20180704 || PACKETVER_ZERO_NUM >= 20181114 static inline uint32 client_nameid( t_itemid server_nameid ){ t_itemid view = itemdb_viewid( server_nameid ); if( view > 0 ){ return view; }else{ return server_nameid; } } #else static inline uint16 client_nameid( t_itemid server_nameid ){ t_itemid view = itemdb_viewid( server_nameid ); if( view > 0 ){ if( view > UINT16_MAX ){ return (uint16)UNKNOWN_ITEM_ID; } return (uint16)view; }else{ if( server_nameid > UINT16_MAX ){ return (uint16)UNKNOWN_ITEM_ID; } return (uint16)server_nameid; } } #endif 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)); } static inline void WFIFOPOS(int fd, unsigned short pos, short x, short y, unsigned char dir) { WBUFPOS(WFIFOP(fd,pos), 0, x, y, dir); } 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 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 RFIFOPOS(int fd, unsigned short pos, short* x, short* y, unsigned char* dir) { RBUFPOS(RFIFOP(fd,pos), 0, x, y, dir); } 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); } //To idenfity disguised characters. static inline bool disguised(struct block_list* bl) { return (bool)( bl->type == BL_PC && ((TBL_PC*)bl)->disguise ); } //Guarantees that the given string does not exceeds the allowed size, as well as making sure it's null terminated. [Skotlex] static inline unsigned int mes_len_check(char* mes, unsigned int len, unsigned int max) { if( len > max ) len = max; mes[len-1] = '\0'; return len; } static char map_ip_str[128]; static uint32 map_ip; static uint32 bind_ip = INADDR_ANY; static uint16 map_port = 5121; static bool clif_ally_only = false; int map_fd; static int clif_parse (int fd); /*========================================== * Ip setting of map-server *------------------------------------------*/ int clif_setip(const char* ip) { char ip_str[16]; map_ip = host2ip(ip); if (!map_ip) { ShowWarning("Failed to Resolve Map Server Address! (%s)\n", ip); return 0; } safestrncpy(map_ip_str, ip, sizeof(map_ip_str)); ShowInfo("Map Server IP Address : '" CL_WHITE "%s" CL_RESET "' -> '" CL_WHITE "%s" CL_RESET "'.\n", ip, ip2str(map_ip, ip_str)); return 1; } void clif_setbindip(const char* ip) { bind_ip = host2ip(ip); if (bind_ip) { char ip_str[16]; ShowInfo("Map Server Bind IP Address : '" CL_WHITE "%s" CL_RESET "' -> '" CL_WHITE "%s" CL_RESET "'.\n", ip, ip2str(bind_ip, ip_str)); } else { ShowWarning("Failed to Resolve Map Server Address! (%s)\n", ip); } } /*========================================== * Sets map port to 'port' * is run from map.cpp upon loading map server configuration *------------------------------------------*/ void clif_setport(uint16 port) { map_port = port; } /*========================================== * Returns map server IP *------------------------------------------*/ uint32 clif_getip(void) { return map_ip; } //Refreshes map_server ip, returns the new ip if the ip changed, otherwise it returns 0. uint32 clif_refresh_ip(void) { uint32 new_ip; new_ip = host2ip(map_ip_str); if (new_ip && new_ip != map_ip) { map_ip = new_ip; ShowInfo("Updating IP resolution of [%s].\n", map_ip_str); return map_ip; } return 0; } /*========================================== * Returns map port which is set by clif_setport() *------------------------------------------*/ uint16 clif_getport(void) { return map_port; } #if PACKETVER >= 20071106 static inline unsigned char clif_bl_type(struct block_list *bl, bool walking) { switch (bl->type) { case BL_PC: return (disguised(bl) && !pcdb_checkid(status_get_viewdata(bl)->class_))? 0x1:0x0; //PC_TYPE case BL_ITEM: return 0x2; //ITEM_TYPE case BL_SKILL: return 0x3; //SKILL_TYPE case BL_CHAT: return 0x4; //UNKNOWN_TYPE case BL_MOB: if( pcdb_checkid( status_get_viewdata( bl )->class_ ) ){ return 0x0; //PC_TYPE }else{ switch( ( (mob_data*)bl )->special_state.ai ){ case AI_ABR: return 0xd; //NPC_ABR_TYPE case AI_BIONIC: return 0xe; //NPC_BIONIC_TYPE default: return 0x5; //NPC_MOB_TYPE } } case BL_NPC: // From 2017-07-26 on NPC type units can also use player sprites. // There is one exception and this is if they are walking. // Since walking NPCs are not supported on official servers, the client does not know how to handle it. #if PACKETVER >= 20170726 return ( pcdb_checkid(status_get_viewdata(bl)->class_) && walking ) ? 0x0 : 0xC; // New walking NPC type #else return pcdb_checkid(status_get_viewdata(bl)->class_) ? 0x0 : 0x6; //NPC_EVT_TYPE #endif case BL_PET: return pcdb_checkid(status_get_viewdata(bl)->class_)?0x0:0x7; //NPC_PET_TYPE case BL_HOM: return 0x8; //NPC_HOM_TYPE case BL_MER: return 0x9; //NPC_MERSOL_TYPE case BL_ELEM: return 0xa; //NPC_ELEMENTAL_TYPE default: return 0x1; //NPC_TYPE } } #endif static bool clif_session_isValid(struct map_session_data *sd) { return ( sd != nullptr && session_isActive(sd->fd) ); } /*========================================== * sub process of clif_send * Called from a map_foreachinallarea (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; unsigned char *buf; int len, type, fd; nullpo_ret(bl); nullpo_ret(sd = (struct map_session_data *)bl); // Don't send to disconnected clients. if( !session_isActive( fd = sd->fd ) ){ return 0; } buf = va_arg(ap,unsigned char*); 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->chatID || bl == src_bl) return 0; break; case AREA_WOSC: { if(src_bl->type == BL_PC) { struct map_session_data *ssd = (struct map_session_data *)src_bl; if (ssd && sd->chatID && (sd->chatID == ssd->chatID)) return 0; } else if(src_bl->type == BL_NPC) { struct npc_data *nd = (struct npc_data *)src_bl; if (nd && sd->chatID && (sd->chatID == nd->chat_id)) return 0; } } break; } /* unless visible, hold it here */ if (!battle_config.update_enemy_position && clif_ally_only && !sd->special_state.intravision && !sd->sc.data[SC_INTRAVISION] && battle_check_target(src_bl,&sd->bl,BCT_ENEMY) > 0) return 0; 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 *------------------------------------------*/ int clif_send(const void* buf, int len, struct block_list* bl, enum send_target type) { int i; struct map_session_data *sd, *tsd; struct party_data *p = NULL; struct guild *g = NULL; std::shared_ptr bg; int x0 = 0, x1 = 0, y0 = 0, y1 = 0, fd; struct s_mapiterator* iter; if( type != ALL_CLIENT ) nullpo_ret(bl); sd = BL_CAST(BL_PC, bl); switch(type) { case ALL_CLIENT: //All player clients. iter = mapit_getallusers(); while( ( tsd = (map_session_data*)mapit_next( iter ) ) != nullptr ){ if( session_isActive( fd = tsd->fd ) ){ WFIFOHEAD( fd, len ); memcpy( WFIFOP( fd, 0 ), buf, len ); WFIFOSET( fd, len ); } } mapit_free(iter); break; case ALL_SAMEMAP: //All players on the same map iter = mapit_getallusers(); while( ( tsd = (map_session_data*)mapit_next( iter ) ) != nullptr ){ if( bl->m == tsd->bl.m && session_isActive( fd = tsd->fd ) ){ WFIFOHEAD( fd, len ); memcpy( WFIFOP( fd, 0 ), buf, len ); WFIFOSET( fd, len ); } } mapit_free(iter); break; case AREA: case AREA_WOSC: if (sd && bl->prev == NULL) //Otherwise source misses the packet.[Skotlex] clif_send (buf, len, bl, SELF); case AREA_WOC: case AREA_WOS: map_foreachinallarea(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: map_foreachinallarea(clif_send_sub, bl->m, bl->x-(AREA_SIZE-5), bl->y-(AREA_SIZE-5), bl->x+(AREA_SIZE-5), bl->y+(AREA_SIZE-5), BL_PC, buf, len, bl, AREA_WOC); break; case CHAT: case CHAT_WOS: { struct chat_data *cd; if (sd) { cd = (struct chat_data*)map_id2bl(sd->chatID); } else if (bl->type == BL_CHAT) { cd = (struct chat_data*)bl; } else break; if (cd == NULL) break; for(i = 0; i < cd->users; i++) { if (type == CHAT_WOS && cd->usersd[i] == sd) continue; if( session_isActive( fd = cd->usersd[i]->fd ) ){ WFIFOHEAD(fd,len); memcpy(WFIFOP(fd,0), buf, len); WFIFOSET(fd,len); } } } break; case PARTY_AREA: case PARTY_AREA_WOS: x0 = bl->x - AREA_SIZE; y0 = bl->y - AREA_SIZE; x1 = bl->x + AREA_SIZE; y1 = bl->y + AREA_SIZE; 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( !session_isActive( 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 (!enable_spy) //Skip unnecessary parsing. [Skotlex] break; iter = mapit_getallusers(); while( ( tsd = (map_session_data*)mapit_next( iter ) ) != nullptr ){ if( tsd->partyspy == p->party.party_id && session_isActive( fd = tsd->fd ) ){ WFIFOHEAD( fd, len ); memcpy( WFIFOP( 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 = (map_session_data*)mapit_next( iter ) ) != nullptr ){ if( type == DUEL_WOS && bl->id == tsd->bl.id ) continue; if( sd->duel_group == tsd->duel_group && session_isActive( fd = tsd->fd ) ){ WFIFOHEAD( fd, len ); memcpy( WFIFOP( fd, 0 ), buf, len ); WFIFOSET( fd, len ); } } mapit_free(iter); break; case SELF: if( clif_session_isValid(sd) ){ fd = sd->fd; 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: x0 = bl->x - AREA_SIZE; y0 = bl->y - AREA_SIZE; x1 = bl->x + AREA_SIZE; y1 = bl->y + AREA_SIZE; 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) != nullptr ){ if( !session_isActive( 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 (!enable_spy) //Skip unnecessary parsing. [Skotlex] break; iter = mapit_getallusers(); while( ( tsd = (map_session_data*)mapit_next( iter ) ) != nullptr ){ if( tsd->guildspy == g->guild_id && session_isActive( fd = tsd->fd ) ){ WFIFOHEAD( fd, len ); memcpy( WFIFOP( fd, 0 ), buf, len ); WFIFOSET( fd, len ); } } mapit_free(iter); } break; case BG_AREA: case BG_AREA_WOS: x0 = bl->x - AREA_SIZE; y0 = bl->y - AREA_SIZE; x1 = bl->x + AREA_SIZE; y1 = bl->y + AREA_SIZE; case BG_SAMEMAP: case BG_SAMEMAP_WOS: case BG: case BG_WOS: if( sd && sd->bg_id > 0 && (bg = util::umap_find(bg_team_db, sd->bg_id))) { for (const auto &member : bg->members) { if( ( sd = member.sd ) == nullptr || !session_isActive( 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 CLAN: if( sd && sd->clan ){ struct clan* clan = sd->clan; for( i = 0; i < clan->max_member; i++ ){ if( ( sd = clan->members[i] ) == nullptr || !session_isActive( fd = sd->fd ) ){ continue; } WFIFOHEAD(fd,len); memcpy(WFIFOP(fd,0), buf, len); WFIFOSET(fd,len); } if (!enable_spy) //Skip unnecessary parsing. [Skotlex] break; iter = mapit_getallusers(); while( ( tsd = (map_session_data*)mapit_next( iter ) ) != nullptr ){ if( tsd->clanspy == clan->id && session_isActive( fd = tsd->fd ) ){ WFIFOHEAD(fd, len); memcpy(WFIFOP(fd, 0), buf, len); WFIFOSET(fd, len); } } mapit_free(iter); } break; default: ShowError("clif_send: Unrecognized type %d\n",type); return -1; } return 0; } /// 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) /// 0a18 .L .3B .B .B .W .B (ZC_ACCEPT_ENTER3) void clif_authok(struct map_session_data *sd) { #if PACKETVER < 20080102 const int cmd = 0x73; #elif PACKETVER < 20141022 || PACKETVER >= 20160330 const int cmd = 0x2eb; #else const int cmd = 0xa18; #endif int fd = sd->fd; WFIFOHEAD(fd,packet_len(cmd)); WFIFOW(fd, 0) = cmd; WFIFOL(fd, 2) = client_tick(gettick()); WFIFOPOS(fd, 6, sd->bl.x, sd->bl.y, sd->ud.dir); WFIFOB(fd, 9) = 5; // ignored WFIFOB(fd,10) = 5; // ignored #if PACKETVER >= 20080102 WFIFOW(fd,11) = sd->status.font; #endif #if PACKETVER >= 20141016 && PACKETVER < 20160330 WFIFOB(fd,13) = sd->status.sex; #endif WFIFOSET(fd,packet_len(cmd)); } /// 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 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 /// 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 /// ? = disconnected -> MsgStringTable[3] void clif_authfail_fd(int fd, int type) { if (!session_isValid(fd) || 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)); set_eof(fd); } /// Notifies the client, whether it can disconnect and change servers (ZC_RESTART_ACK). /// 00b3 .B /// type: /// 1 = disconnect, char-select /// ? = nothing void clif_charselectok(int id, uint8 ok) { struct map_session_data* sd; int fd; if ((sd = map_id2sd(id)) == NULL || !session_isValid(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 .L .W .W .B .W .W .B .B .W (ZC_ITEM_FALL_ENTRY4) /// 0ADD .L .W .W .B .W .W .B .B .W .B .W (ZC_ITEM_FALL_ENTRY5) void clif_dropflooritem( struct flooritem_data* fitem, bool canShowEffect ){ nullpo_retv(fitem); if( fitem->item.nameid == 0 ){ return; } struct packet_dropflooritem p; p.PacketType = dropflooritemType; p.ITAID = fitem->bl.id; p.ITID = client_nameid( fitem->item.nameid ); #if PACKETVER >= 20130000 /* not sure date */ p.type = itemtype( fitem->item.nameid ); #endif p.IsIdentified = fitem->item.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.amount; #if defined(PACKETVER_ZERO) || PACKETVER >= 20180418 if( canShowEffect ){ uint8 dropEffect = itemdb_dropeffect( fitem->item.nameid ); if( dropEffect > 0 ){ p.showdropeffect = 1; p.dropeffectmode = dropEffect - 1; }else if (battle_config.rndopt_drop_pillar != 0){ uint8 optionCount = 0; for (uint8 i = 0; i < MAX_ITEM_RDM_OPT; i++) { if (fitem->item.option[i].id != 0) { optionCount++; } } if (optionCount > 0) { p.showdropeffect = 1; if (optionCount == 1) p.dropeffectmode = DROPEFFECT_BLUE_PILLAR - 1; else if (optionCount == 2) p.dropeffectmode = DROPEFFECT_YELLOW_PILLAR - 1; else p.dropeffectmode = DROPEFFECT_PURPLE_PILLAR - 1; } else { p.showdropeffect = 0; p.dropeffectmode = DROPEFFECT_NONE; } } else { p.showdropeffect = 0; p.dropeffectmode = DROPEFFECT_NONE; } }else{ p.showdropeffect = 0; p.dropeffectmode = DROPEFFECT_NONE; } #endif clif_send( &p, sizeof(p), &fitem->bl, AREA ); } /// Makes an item disappear from the ground. /// 00a1 .L (ZC_ITEM_DISAPPEAR) 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 ( !session_isActive( fd ) ){ 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 void clif_clearunit_single(int id, 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 void clif_clearunit_area(struct block_list* bl, clr_type type) { unsigned char buf[8]; nullpo_retv(bl); WBUFW(buf,0) = 0x80; WBUFL(buf,2) = bl->id; WBUFB(buf,6) = type; clif_send(buf, packet_len(0x80), bl, type == CLR_DEAD ? AREA : AREA_WOS); if(disguised(bl)) { WBUFL(buf,2) = disguised_bl_id( 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 TIMER_FUNC(clif_clearunit_delayed_sub){ struct block_list *bl = (struct block_list *)data; clif_clearunit_area(bl, (clr_type) id); ers_free(delay_clearunit_ers,bl); return 0; } void clif_clearunit_delayed(struct block_list* bl, clr_type type, t_tick tick) { struct block_list *tbl = ers_alloc(delay_clearunit_ers, struct block_list); memcpy (tbl, bl, sizeof (struct block_list)); add_timer(tick, clif_clearunit_delayed_sub, (int)type, (intptr_t)tbl); } void clif_get_weapon_view(struct map_session_data* sd, t_itemid *rhand, t_itemid *lhand) { if(sd->sc.option&OPTION_COSTUME) { *rhand = *lhand = 0; return; } #if PACKETVER < 4 *rhand = sd->status.weapon; *lhand = sd->status.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); 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; } /*========================================== * Prepares 'unit standing/spawning' packet *------------------------------------------*/ static void clif_set_unit_idle( struct block_list* bl, bool walking, send_target target, struct block_list* tbl ){ nullpo_retv( bl ); struct map_session_data* sd = BL_CAST( BL_PC, bl ); struct status_change* sc = status_get_sc( bl ); struct view_data* vd = status_get_viewdata( bl ); int g_id = status_get_guild_id( bl ); #if PACKETVER < 20091103 if( !pcdb_checkid( vd->class_ ) ){ struct packet_idle_unit2 p; p.PacketType = idle_unit2Type; #if PACKETVER >= 20071106 p.objecttype = clif_bl_type( bl, walking ); #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 ) ? sc->option : 0; p.job = vd->class_; p.head = vd->hair_style; p.weapon = vd->weapon; p.accessory = vd->head_bottom; if( bl->type == BL_NPC && vd->class_ == JT_GUILD_FLAG ){ // 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 ); }else{ p.shield = vd->shield; 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; 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 ), tbl, target ); return; } #endif struct packet_idle_unit p; p.PacketType = idle_unitType; #if PACKETVER >= 20091103 p.PacketLength = sizeof(p); p.objecttype = clif_bl_type( bl, walking ); #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; // npc option changed? if( tbl && tbl->type == BL_PC && bl->type == BL_NPC ){ struct map_session_data* sd = (struct map_session_data*)tbl; struct npc_data* nd = (struct npc_data*)bl; int option = (sc) ? sc->option : 0; if( !nd->vd.dead_sit ){ if( std::find( sd->cloaked_npc.begin(), sd->cloaked_npc.end(), nd->bl.id ) != sd->cloaked_npc.end() ){ option ^= OPTION_CLOAK; } } p.effectState = option; }else{ p.effectState = (sc) ? sc->option : 0; } p.job = vd->class_; p.head = vd->hair_style; p.weapon = vd->weapon; #if PACKETVER < 7 || PACKETVER_MAIN_NUM >= 20181121 || PACKETVER_RE_NUM >= 20180704 || PACKETVER_ZERO_NUM >= 20181114 p.shield = vd->shield; #endif if( bl->type == BL_NPC && vd->class_ == JT_GUILD_FLAG ){ // 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 ); }else{ p.accessory = vd->head_bottom; 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; 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.monster_hp_bars_info && !map_getmapflag( bl->m, MF_HIDEMOBHPBAR ) && 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 ){ p.isBoss = ( (mob_data*)bl )->get_bosstype(); }else if( bl->type == BL_PET ){ p.isBoss = ( (pet_data*)bl )->db->get_bosstype(); }else{ p.isBoss = BOSSTYPE_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, status_get_name( bl ), NAME_LENGTH); #endif clif_send( &p, sizeof( p ), tbl, target ); // if disguised, send to self if( disguised( bl ) ){ #if PACKETVER >= 20091103 p.objecttype = pcdb_checkid( status_get_viewdata( bl )->class_ ) ? 0x0 : 0x5; //PC_TYPE : NPC_MOB_TYPE #if PACKETVER >= 20131223 p.AID = disguised_bl_id( bl->id ); #else p.GID = disguised_bl_id( bl->id ); #endif #else p.GID = disguised_bl_id( bl->id ); #endif clif_send(&p, sizeof(p), bl, SELF); } } static void clif_spawn_unit( struct block_list *bl, enum send_target target ){ nullpo_retv( bl ); struct map_session_data* sd = BL_CAST( BL_PC, bl ); struct status_change* sc = status_get_sc( bl ); struct view_data* vd = status_get_viewdata( bl ); int g_id = status_get_guild_id( bl ); #if PACKETVER < 20091103 if( !pcdb_checkid( vd->class_ ) ){ struct packet_spawn_unit2 p; p.PacketType = spawn_unit2Type; #if PACKETVER >= 20071106 p.objecttype = clif_bl_type( bl, false ); #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 ) ? sc->option : 0; p.head = vd->hair_style; p.weapon = vd->weapon; p.accessory = vd->head_bottom; p.job = vd->class_; if( bl->type == BL_NPC && vd->class_ == JT_GUILD_FLAG ){ // 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 ); }else{ p.shield = vd->shield; 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; 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 ); return; } #endif struct packet_spawn_unit p; p.PacketType = spawn_unitType; #if PACKETVER >= 20091103 p.PacketLength = sizeof(p); p.objecttype = clif_bl_type( bl, false ); #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) ? sc->option : 0; p.job = vd->class_; p.head = vd->hair_style; p.weapon = vd->weapon; #if PACKETVER < 7 || PACKETVER_MAIN_NUM >= 20181121 || PACKETVER_RE_NUM >= 20180704 || PACKETVER_ZERO_NUM >= 20181114 p.shield = vd->shield; #endif if( bl->type == BL_NPC && vd->class_ == JT_GUILD_FLAG ){ // 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 ); }else{ p.accessory = vd->head_bottom; 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; 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.monster_hp_bars_info && bl->type == BL_MOB && !map_getmapflag( bl->m, MF_HIDEMOBHPBAR ) && ( 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 ){ p.isBoss = ( (mob_data*)bl )->get_bosstype(); }else if( bl->type == BL_PET ){ p.isBoss = ( (pet_data*)bl )->db->get_bosstype(); }else{ p.isBoss = BOSSTYPE_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, status_get_name( bl ), NAME_LENGTH ); #endif if( disguised( bl ) ){ nullpo_retv( sd ); if( sd->status.class_ != sd->disguise ){ clif_send( &p, sizeof( p ), bl, target ); } #if PACKETVER >= 20091103 p.objecttype = pcdb_checkid( status_get_viewdata(bl)->class_ ) ? 0x0 : 0x5; //PC_TYPE : NPC_MOB_TYPE #if PACKETVER >= 20131223 p.AID = disguised_bl_id( bl->id ); #else p.GID = disguised_bl_id( bl->id ); #endif #else p.GID = disguised_bl_id( 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 ){ nullpo_retv( bl ); nullpo_retv( ud ); 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); 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, true ); #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) ? sc->option : 0; p.job = vd->class_; p.head = vd->hair_style; p.weapon = vd->weapon; p.accessory = vd->head_bottom; p.moveStartTime = client_tick(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.monster_hp_bars_info && !map_getmapflag(bl->m, MF_HIDEMOBHPBAR) && 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 ){ p.isBoss = ( (mob_data*)bl )->get_bosstype(); }else if( bl->type == BL_PET ){ p.isBoss = ( (pet_data*)bl )->db->get_bosstype(); }else{ p.isBoss = BOSSTYPE_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, status_get_name(bl), NAME_LENGTH); #endif clif_send( &p, sizeof(p), tsd ? &tsd->bl : bl, target ); // if disguised, send the info to self if( disguised( bl ) ){ #if PACKETVER >= 20091103 p.objecttype = pcdb_checkid( status_get_viewdata(bl)->class_ ) ? 0x0 : 0x5; //PC_TYPE : NPC_MOB_TYPE #if PACKETVER >= 20131223 p.AID = disguised_bl_id( bl->id ); #else p.GID = disguised_bl_id( bl->id ); #endif #else p.GID = disguised_bl_id( bl->id ); #endif clif_send(&p,sizeof(p),bl,SELF); } } //Modifies the buffer for disguise characters and sends it to self. //Used for spawn/walk packets, where the ID offset changes for packetver >=9 static void clif_setdisguise(struct block_list *bl, unsigned char *buf,int len) { #if PACKETVER >= 20091103 WBUFB(buf,4)= pcdb_checkid(status_get_viewdata(bl)->class_) ? 0x0 : 0x5; //PC_TYPE : NPC_MOB_TYPE WBUFL(buf,5)=-bl->id; #elif PACKETVER >= 20071106 WBUFB(buf,2)= pcdb_checkid(status_get_viewdata(bl)->class_) ? 0x0 : 0x5; //PC_TYPE : NPC_MOB_TYPE WBUFL(buf,3)=-bl->id; #else WBUFL(buf,2)=-bl->id; #endif clif_send(buf, len, bl, SELF); } /// Changes sprite of an NPC object (ZC_NPCSPRITE_CHANGE). /// 01b0 .L .B .L /// type: /// unused void clif_class_change_target(struct block_list *bl,int class_,int type, enum send_target target, struct map_session_data *sd) { nullpo_retv(bl); if(!pcdb_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_; clif_send(buf,packet_len(0x1b0),(sd == NULL ? bl : &(sd->bl)),target); } } void clif_servantball( struct map_session_data& sd, struct block_list* target, enum send_target send_target ){ struct PACKET_ZC_SPIRITS p = {}; p.packetType = HEADER_ZC_SPIRITS; p.GID = sd.bl.id; p.amount = sd.servantball; if( target == nullptr ){ target = &sd.bl; } clif_send( &p, sizeof( p ), target, send_target ); } void clif_abyssball( struct map_session_data& sd, struct block_list* target, enum send_target send_target ){ struct PACKET_ZC_SPIRITS p = {}; p.packetType = HEADER_ZC_SPIRITS; p.GID = sd.bl.id; p.amount = sd.abyssball; if( target == nullptr ){ target = &sd.bl; } clif_send( &p, sizeof( p ), target, send_target ); } /// Notifies the client of an object's Millenium Shields. static void clif_millenniumshield_single(int fd, map_session_data *sd) { nullpo_retv(sd); if (sd->sc.data[SC_MILLENNIUMSHIELD] == nullptr) return; WFIFOHEAD(fd, packet_len(0x440)); WFIFOW(fd, 0) = 0x440; WFIFOL(fd, 2) = sd->bl.id; WFIFOW(fd, 6) = sd->sc.data[SC_MILLENNIUMSHIELD]->val2; WFIFOW(fd, 8) = 0; WFIFOSET(fd, packet_len(0x440)); } /*========================================== * Kagerou/Oboro amulet spirit *------------------------------------------*/ static void clif_spiritcharm_single(int fd, struct map_session_data *sd) { WFIFOHEAD(fd, packet_len(0x08cf)); WFIFOW(fd,0)=0x08cf; WFIFOL(fd,2)=sd->bl.id; WFIFOW(fd,6)=sd->spiritcharm_type; WFIFOW(fd,8)=sd->spiritcharm; WFIFOSET(fd, packet_len(0x08cf)); } /*========================================== * Enchanting Shadow / Shadow Scar Spirit *------------------------------------------*/ void clif_enchantingshadow_spirit(unit_data &ud) { #if PACKETVER_MAIN_NUM >= 20191120 || PACKETVER_RE_NUM >= 20191120 || PACKETVER_ZERO_NUM >= 20191127 PACKET_ZC_TARGET_SPIRITS p = {}; p.packetType = HEADER_ZC_TARGET_SPIRITS; p.GID = ud.bl->id; p.unknown_val = 0; p.amount = static_cast(ud.shadow_scar_timer.size()); clif_send(&p, sizeof(p), ud.bl, AREA); #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 = sd->bl.m; int fd = sd->fd; if (map_getmapflag(m, MF_SNOW) || map_getmapflag(m, MF_CLOUDS) || map_getmapflag(m, MF_FOG) || map_getmapflag(m, MF_FIREWORKS) || map_getmapflag(m, MF_SAKURA) || map_getmapflag(m, MF_LEAVES) || map_getmapflag(m, MF_CLOUDS2)) { if (map_getmapflag(m, MF_SNOW)) clif_specialeffect_single(&sd->bl, EF_SNOW, fd); if (map_getmapflag(m, MF_CLOUDS)) clif_specialeffect_single(&sd->bl, EF_CLOUD3, fd); if (map_getmapflag(m, MF_CLOUDS2)) clif_specialeffect_single(&sd->bl, EF_CLOUD5, fd); if (map_getmapflag(m, MF_FOG)) clif_specialeffect_single(&sd->bl, EF_CLOUD4, fd); if (map_getmapflag(m, MF_FIREWORKS)) { clif_specialeffect_single(&sd->bl, EF_POKJUK, fd); clif_specialeffect_single(&sd->bl, EF_THROWITEM2, fd); clif_specialeffect_single(&sd->bl, EF_POKJUK_SOUND, fd); } if (map_getmapflag(m, MF_SAKURA)) clif_specialeffect_single(&sd->bl, EF_SAKURA, fd); if (map_getmapflag(m, MF_LEAVES)) clif_specialeffect_single(&sd->bl, EF_MAPLE, fd); } } /** * Run when the weather on a map changes, throws all players in map id 'm' to clif_weather_check function **/ void clif_weather(int16 m) { struct s_mapiterator* iter; struct map_session_data *sd=NULL; iter = mapit_getallusers(); for( sd = (struct map_session_data*)mapit_first(iter); mapit_exists(iter); sd = (struct map_session_data*)mapit_next(iter) ) { if( sd->bl.m == m ) clif_weather_check(sd); } mapit_free(iter); } /** * Hide a NPC from the effects of Maya Purple card. * @param bl: Block data * @return True if NPC is disabled or false otherwise */ static inline bool clif_npc_mayapurple(block_list *bl) { nullpo_retr(false, bl); if (bl->type == BL_NPC) { npc_data *nd = map_id2nd(bl->id); // TODO: Confirm if waitingroom cause any special cases if (/* nd->chat_id == 0 && */ nd->sc.option & OPTION_INVISIBLE) return true; } return false; } /** * Main function to spawn a unit on the client (player/mob/pet/etc) **/ int clif_spawn( struct block_list *bl, bool walking ){ struct view_data *vd; vd = status_get_viewdata(bl); if( !vd || vd->class_ == JT_INVISIBLE ) return 0; // Hide NPC from Maya Purple card if (clif_npc_mayapurple(bl)) return 0; if( bl->type == BL_NPC && !vd->dead_sit ){ clif_set_unit_idle( bl, walking, AREA_WOS, bl ); }else{ 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: { TBL_PC *sd = ((TBL_PC*)bl); if (sd->spiritball > 0) clif_spiritball(&sd->bl); if (sd->sc.data[SC_MILLENNIUMSHIELD]) clif_millenniumshield(&sd->bl, sd->sc.data[SC_MILLENNIUMSHIELD]->val2); if (sd->soulball > 0) clif_soulball(sd); if (sd->servantball > 0) clif_servantball( *sd ); if (sd->abyssball > 0) clif_abyssball( *sd ); if(sd->state.size==SZ_BIG) // tiny/big players [Valaris] clif_specialeffect(bl,EF_GIANTBODY2,AREA); else if(sd->state.size==SZ_MEDIUM) clif_specialeffect(bl,EF_BABYBODY2,AREA); if( sd->bg_id && map_getmapflag(sd->bl.m, MF_BATTLEGROUND) ) clif_sendbgemblem_area(sd); if (sd->spiritcharm_type != CHARM_TYPE_NONE && sd->spiritcharm > 0) clif_spiritcharm(sd); if (sd->status.robe) clif_refreshlook(bl,bl->id,LOOK_ROBE,sd->status.robe,AREA); clif_efst_status_change_sub(bl, bl, AREA); clif_hat_effects(sd,bl,AREA); } break; case BL_MOB: { TBL_MOB *md = ((TBL_MOB*)bl); if(md->special_state.size==SZ_BIG) // tiny/big mobs [Valaris] clif_specialeffect(&md->bl,EF_GIANTBODY2,AREA); else if(md->special_state.size==SZ_MEDIUM) clif_specialeffect(&md->bl,EF_BABYBODY2,AREA); if ( md->special_state.ai == AI_ABR || md->special_state.ai == AI_BIONIC ) clif_summon_init(*md); } break; case BL_NPC: { TBL_NPC *nd = ((TBL_NPC*)bl); if( nd->size == SZ_BIG ) clif_specialeffect(&nd->bl,EF_GIANTBODY2,AREA); else if( nd->size == SZ_MEDIUM ) clif_specialeffect(&nd->bl,EF_BABYBODY2,AREA); clif_efst_status_change_sub(bl, bl, AREA); clif_progressbar_npc_area(nd); } break; case BL_PET: if (vd->head_bottom) clif_pet_equip_area((TBL_PET*)bl); // needed to display pet equip properly break; } return 0; } /// Sends information about owned homunculus to the client . [orn] /// 022e .24B .B .W .W .W .W .W .W .W .W .W .W .W .W .W .W .W .W .L .L .W .W (ZC_PROPERTY_HOMUN) /// 09f7 .24B .B .W .W .W .W .W .W .W .W .W .W .W .W .L .L .W .W .L .L .W .W (ZC_PROPERTY_HOMUN_2) 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) nullpo_retv( sd ); nullpo_retv( hd ); struct status_data *status = &hd->battle_status; struct PACKET_ZC_PROPERTY_HOMUN p; p.packetType = HEADER_ZC_PROPERTY_HOMUN; safestrncpy( p.name, hd->homunculus.name, sizeof( p.name ) ); // 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 p.atk2 = cap_value( status->rhw.atk2 + status->batk, 0, INT16_MAX ); p.matk = i16min( status->matk_max, INT16_MAX ); //FIXME capping to INT16 here is too late p.hit = status->hit; if( battle_config.hom_setting&HOMSET_DISPLAY_LUK ){ p.crit = status->luk / 3 + 1; //crit is a +1 decimal value! Just display purpose.[Vicious] }else{ p.crit = status->cri / 10; } #ifdef RENEWAL p.def = status->def + status->def2; p.mdef = status->mdef + status->mdef2; #else p.def = status->def + status->vit; p.mdef = status->mdef; #endif p.flee = status->flee; p.amotion = (flag) ? 0 : status->amotion; #if PACKETVER >= 20141016 // Homunculus HP bar will screw up if the percentage calculation exceeds signed values // Tested maximum: 21474836(=INT32_MAX/100), any value above will screw up the HP bar if( status->max_hp > ( INT32_MAX / 100 ) ){ p.hp = status->hp / ( status->max_hp / 100 ); p.maxHp = 100; }else{ p.hp = status->hp; p.maxHp = status->max_hp; } #else if( status->max_hp > INT16_MAX ){ p.hp = status->hp / ( status->max_hp / 100 ); p.maxHp = 100; }else{ p.hp = status->hp; p.maxHp = status->max_hp; } #endif if( status->max_sp > INT16_MAX ){ p.sp = status->sp / ( status->max_sp / 100 ); p.maxSp = 100; }else{ p.sp = status->sp; p.maxSp = status->max_sp; } #if PACKETVER_MAIN_NUM >= 20210303 || PACKETVER_RE_NUM >= 20211103 p.exp = hd->homunculus.exp; p.expNext = hd->exp_next; #else p.exp = (uint32)hd->homunculus.exp; p.expNext = (uint32)hd->exp_next; #endif switch( hom_class2type( hd->homunculus.class_ ) ){ 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 void clif_send_homdata(struct map_session_data *sd, int state, int param) { //[orn] int fd = sd->fd; if ( (state == SP_INTIMATE) && (param >= 910) && (sd->hd->homunculus.class_ == sd->hd->homunculusDB->evo_class) ) hom_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)); } int clif_homskillinfoblock(struct map_session_data *sd) { //[orn] struct homun_data *hd; int fd = sd->fd; int i, len=4; nullpo_ret(sd); hd = sd->hd; if ( !hd ) return 0 ; 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 ){ int combo = (hd->homunculus.hskill[i].flag)&SKILL_FLAG_TMP_COMBO; short idx = hom_skill_get_index(id); if (idx == -1) continue; WFIFOW(fd,len ) = id; WFIFOW(fd,len+2) = ((combo)?INF_SELF_SKILL:skill_get_inf(id)); WFIFOW(fd,len+4) = 0; WFIFOW(fd,len+6) = hd->homunculus.hskill[idx].lv; WFIFOW(fd,len+8) = skill_get_sp(id,hd->homunculus.hskill[idx].lv); WFIFOW(fd,len+10)= skill_get_range2(&sd->hd->bl,id,hd->homunculus.hskill[idx].lv,false); safestrncpy(WFIFOCP(fd,len+12), skill_get_name(id), NAME_LENGTH); WFIFOB(fd,len+36) = (hd->homunculus.level < hom_skill_get_min_level(hd->homunculus.class_, id) || hd->homunculus.hskill[idx].lv >= hom_skill_tree_get_max(id, hd->homunculus.class_)) ? 0 : 1; len+=37; } } WFIFOW(fd,2)=len; WFIFOSET(fd,len); return 0; } void clif_homskillup(struct map_session_data *sd, uint16 skill_id) { //[orn] struct homun_data *hd; int fd; short idx = -1; nullpo_retv(sd); if ((idx = hom_skill_get_index(skill_id)) == -1) return; 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,false); WFIFOB(fd,10) = (hd->homunculus.level < hom_skill_get_min_level(hd->homunculus.class_, skill_id) || hd->homunculus.hskill[idx].lv >= hom_skill_tree_get_max(hd->homunculus.hskill[idx].id, hd->homunculus.class_)) ? 0 : 1; WFIFOSET(fd,packet_len(0x239)); } /// Result of request to feed a homun/merc. /// 022f .B .W (ZC_FEED_MER) /// result: /// 0 = failure /// 1 = success void clif_hom_food( struct map_session_data *sd, int foodid, int fail ){ nullpo_retv( sd ); struct PACKET_ZC_FEED_MER p; p.packetType = 0x22f; p.result = fail; p.itemId = client_nameid( foodid ); clif_send( &p, sizeof( p ), &sd->bl, SELF ); } /// Notifies the client, that it is walking (ZC_NOTIFY_PLAYERMOVE). /// 0087 .L .6B void clif_walkok(struct map_session_data *sd) { int fd=sd->fd; WFIFOHEAD(fd, packet_len(0x87)); WFIFOW(fd,0)=0x87; WFIFOL(fd,2)=client_tick(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 ){ struct status_change *sc = NULL; if ((sc = status_get_sc(bl)) && sc->option&(OPTION_HIDE|OPTION_CLOAK|OPTION_INVISIBLE|OPTION_CHASEWALK)) clif_ally_only = true; clif_set_unit_walking( bl, nullptr, 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: { TBL_PC *sd = ((TBL_PC*)bl); // clif_movepc(sd); if(sd->state.size==SZ_BIG) // tiny/big players [Valaris] clif_specialeffect(&sd->bl,EF_GIANTBODY2,AREA); else if(sd->state.size==SZ_MEDIUM) clif_specialeffect(&sd->bl,EF_BABYBODY2,AREA); if (sd->status.robe) clif_refreshlook(bl,bl->id,LOOK_ROBE,sd->status.robe,AREA); } break; case BL_MOB: { TBL_MOB *md = ((TBL_MOB*)bl); if(md->special_state.size==SZ_BIG) // tiny/big mobs [Valaris] clif_specialeffect(&md->bl,EF_GIANTBODY2,AREA); else if(md->special_state.size==SZ_MEDIUM) clif_specialeffect(&md->bl,EF_BABYBODY2,AREA); } break; case BL_PET: if(vd->head_bottom) // needed to display pet equip properly clif_pet_equip_area((TBL_PET*)bl); break; } clif_ally_only = false; } /// 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 void clif_move(struct unit_data *ud) { unsigned char buf[16]; struct view_data* vd; struct block_list* bl = ud->bl; struct status_change *sc = NULL; vd = status_get_viewdata(bl); if (!vd ) return; //This performance check is needed to keep GM-hidden objects from being notified to bots. else if( vd->class_ == JT_INVISIBLE ){ // If the player was disguised we still need to update the disguised unit, since the main unit will be updated through clif_walkok if(disguised(bl)) { WBUFW(buf,0)=0x86; WBUFL(buf,2)=disguised_bl_id(bl->id); WBUFPOS2(buf,6,bl->x,bl->y,ud->to_x,ud->to_y,8,8); WBUFL(buf,12)=client_tick(gettick()); clif_send(buf, packet_len(0x86), bl, SELF); } return; } // Hide NPC from Maya Purple card if (clif_npc_mayapurple(bl)) 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; } if ((sc = status_get_sc(bl)) && sc->option&(OPTION_HIDE|OPTION_CLOAK|OPTION_INVISIBLE|OPTION_CHASEWALK)) clif_ally_only = true; 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)=client_tick(gettick()); clif_send(buf, packet_len(0x86), bl, AREA_WOS); if (disguised(bl)) { WBUFL(buf,2)=disguised_bl_id(bl->id); clif_send(buf, packet_len(0x86), bl, SELF); } clif_ally_only = false; } /*========================================== * Delays the map_quit of a player after they are disconnected. [Skotlex] *------------------------------------------*/ static TIMER_FUNC(clif_delayquit){ 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; } /*========================================== * *------------------------------------------*/ void clif_quitsave(int fd,struct map_session_data *sd) { if (!battle_config.prevent_logout || sd->canlog_tick == 0 || DIFF_TICK(gettick(), sd->canlog_tick) > battle_config.prevent_logout) map_quit(sd); else if (session_isValid(sd->fd)) { //Disassociate session from player (session is deleted after this function was called) //And set a timer to make him quit later. session[sd->fd]->session_data = NULL; sd->fd = 0; 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 .16B .W .W void clif_changemap(struct map_session_data *sd, short m, int x, int y) { nullpo_retv(sd); int fd = sd->fd; WFIFOHEAD(fd,packet_len(0x91)); WFIFOW(fd,0) = 0x91; mapindex_getmapname_ext(map_mapid2mapname(m), WFIFOCP(fd,2)); WFIFOW(fd,18) = x; WFIFOW(fd,20) = y; WFIFOSET(fd,packet_len(0x91)); } /// Notifies the client of a position change to coordinates on given map, which is on another map-server. /// 0092 .16B .W .W .L .W (ZC_NPCACK_SERVERMOVE) /// 0ac7 .16B .W .W .L .W .128B (ZC_NPCACK_SERVERMOVE2) void clif_changemapserver(struct map_session_data* sd, unsigned short map_index, int x, int y, uint32 ip, uint16 port) { int fd; #if PACKETVER >= 20170315 int cmd = 0xac7; #else int cmd = 0x92; #endif nullpo_retv(sd); fd = sd->fd; WFIFOHEAD(fd,packet_len(cmd)); WFIFOW(fd,0) = cmd; mapindex_getmapname_ext(mapindex_id2name(map_index), WFIFOCP(fd,2)); WFIFOW(fd,18) = x; WFIFOW(fd,20) = y; WFIFOL(fd,22) = htonl(ip); WFIFOW(fd,26) = ntows(htons(port)); // [!] LE byte order here [!] #if PACKETVER >= 20170315 memset(WFIFOP(fd, 28), 0, 128); // Unknown #endif WFIFOSET(fd,packet_len(cmd)); } /// In many situations (knockback, backslide, etc.) Aegis sends both clif_slide and clif_fixpos /// This function combines both calls and allows to simplify the calling code void clif_blown(struct block_list *bl) { clif_slide(bl, bl->x, bl->y); clif_fixpos(bl); } /// Visually moves(slides) a character to x,y. If the target cell /// isn't walkable, the char doesn't move at all. If the char is /// sitting it will stand up (ZC_STOPMOVE). /// 0088 .L .W .W void clif_fixpos(struct block_list *bl) { unsigned char buf[10]; nullpo_retv(bl); WBUFW(buf,0) = 0x88; WBUFL(buf,2) = bl->id; WBUFW(buf,6) = bl->x; WBUFW(buf,8) = bl->y; clif_send(buf, packet_len(0x88), bl, AREA); if( disguised(bl) ) { WBUFL(buf,2) = disguised_bl_id(bl->id); clif_send(buf, packet_len(0x88), bl, SELF); } } /// Displays the buy/sell dialog of an NPC shop (ZC_SELECT_DEALTYPE). /// 00c4 .L void clif_npcbuysell(struct map_session_data* sd, int id) { int fd; nullpo_retv(sd); fd=sd->fd; WFIFOHEAD(fd, packet_len(0xc4)); WFIFOW(fd,0)=0xc4; WFIFOL(fd,2)=id; WFIFOSET(fd,packet_len(0xc4)); } /// Presents list of items, that can be bought in an NPC shop. /// 00c6 .W { .L .L .B .W }* (ZC_PC_PURCHASE_ITEMLIST) void clif_buylist( struct map_session_data *sd, struct npc_data *nd ){ nullpo_retv( sd ); nullpo_retv( nd ); int fd = sd->fd; if( !session_isActive( fd ) ){ return; } uint16 len = sizeof( struct PACKET_ZC_PC_PURCHASE_ITEMLIST ) + nd->u.shop.count * sizeof( struct PACKET_ZC_PC_PURCHASE_ITEMLIST_sub ); WFIFOHEAD( fd, len ); struct PACKET_ZC_PC_PURCHASE_ITEMLIST *p = (struct PACKET_ZC_PC_PURCHASE_ITEMLIST *)WFIFOP( fd, 0 ); p->packetType = HEADER_ZC_PC_PURCHASE_ITEMLIST; int count = 0; for( int i = 0, discount = npc_shop_discount( nd ); i < nd->u.shop.count; i++ ){ int val = nd->u.shop.shop_item[i].value; p->items[count].price = val; p->items[count].discountPrice = ( discount ) ? pc_modifybuyvalue( sd, val ) : val; p->items[count].itemType = itemtype( nd->u.shop.shop_item[i].nameid ); p->items[count].itemId = client_nameid( nd->u.shop.shop_item[i].nameid ); #if PACKETVER_MAIN_NUM >= 20210203 || PACKETVER_RE_NUM >= 20211103 std::shared_ptr id = item_db.find(nd->u.shop.shop_item[i].nameid); p->items[count].viewSprite = id->look; p->items[count].location = pc_equippoint_sub( sd, id.get() ); #endif count++; } p->packetLength = sizeof( struct PACKET_ZC_PC_PURCHASE_ITEMLIST ) + count * sizeof( struct PACKET_ZC_PC_PURCHASE_ITEMLIST_sub ); WFIFOSET( fd, p->packetLength ); } /// Presents list of items, that can be sold to an NPC shop (ZC_PC_SELL_ITEMLIST). /// 00c7 .W { .W .L .L }* void clif_selllist(struct map_session_data *sd) { int fd,i,c=0,val; struct npc_data *nd; nullpo_retv(sd); if (!sd->npc_shopid || (nd = map_id2nd(sd->npc_shopid)) == NULL) return; fd=sd->fd; WFIFOHEAD(fd, MAX_INVENTORY * 10 + 4); WFIFOW(fd,0)=0xc7; for( i = 0; i < MAX_INVENTORY; i++ ) { if( sd->inventory.u.items_inventory[i].nameid > 0 && sd->inventory_data[i] ) { if( !pc_can_sell_item(sd, &sd->inventory.u.items_inventory[i], nd->subtype)) continue; if (battle_config.rental_item_novalue && sd->inventory.u.items_inventory[i].expire_time) val = 0; else { val = sd->inventory_data[i]->value_sell; if( val < 0 ) continue; } WFIFOW(fd,4+c*10)=i+2; WFIFOL(fd,6+c*10)=val; WFIFOL(fd,10+c*10)=pc_modifysellvalue(sd,val); c++; } } WFIFOW(fd,2)=c*10+4; WFIFOSET(fd,WFIFOW(fd,2)); } /// Closes shop (CZ_NPC_TRADE_QUIT). /// 09d4 void clif_parse_NPCShopClosed(int fd, struct map_session_data *sd) { // TODO: State tracking? sd->npc_shopid = 0; } /** * Presents list of items, that can be sold to a Market shop. * @author: Ind and Yommy **/ /// 0x9d5 .W { .W .B .L .L .W }* (ZC_NPC_MARKET_OPEN) void clif_npc_market_open(struct map_session_data *sd, struct npc_data *nd) { #if PACKETVER >= 20131223 nullpo_retv( sd ); nullpo_retv( nd ); if( sd->state.trading ){ return; } int fd = sd->fd; WFIFOHEAD( fd, sizeof( struct PACKET_ZC_NPC_MARKET_OPEN ) + nd->u.shop.count * sizeof( struct PACKET_ZC_NPC_MARKET_OPEN_sub ) ); struct PACKET_ZC_NPC_MARKET_OPEN *p = (struct PACKET_ZC_NPC_MARKET_OPEN *)WFIFOP( fd, 0 ); p->packetType = HEADER_ZC_NPC_MARKET_OPEN; int count = 0; for( int i = 0; i < nd->u.shop.count; i++ ){ struct npc_item_list *item = &nd->u.shop.shop_item[i]; if( !item->nameid ){ continue; } std::shared_ptr id = item_db.find(item->nameid); if( !id ){ continue; } // Out of stock if( item->qty == 0 ){ continue; } p->list[count].nameid = client_nameid( item->nameid ); p->list[count].type = itemtype( item->nameid ); p->list[count].price = item->value; p->list[count].qty = item->qty; p->list[count].weight = id->weight; #if PACKETVER_MAIN_NUM >= 20210203 || PACKETVER_RE_NUM >= 20211103 p->list[count].location = pc_equippoint_sub( sd, id.get() ); #endif count++; } p->packetLength = sizeof( struct PACKET_ZC_NPC_MARKET_OPEN ) + count * sizeof( struct PACKET_ZC_NPC_MARKET_OPEN_sub ); WFIFOSET( fd, p->packetLength ); sd->state.trading = 1; #endif } /// Closes the Market shop window. void clif_parse_NPCMarketClosed(int fd, struct map_session_data *sd) { nullpo_retv(sd); sd->npc_shopid = 0; sd->state.trading = 0; } /// Purchase item from Market shop. /// 0x9d7 .W .B { .W .W .L }* (ZC_NPC_MARKET_PURCHASE_RESULT) void clif_npc_market_purchase_ack( struct map_session_data *sd, e_purchase_result res, std::vector& list ){ #if PACKETVER >= 20131223 nullpo_retv( sd ); struct npc_data *nd = map_id2nd( sd->npc_shopid ); nullpo_retv( nd ); struct PACKET_ZC_NPC_MARKET_PURCHASE_RESULT *p = (struct PACKET_ZC_NPC_MARKET_PURCHASE_RESULT *)packet_buffer; p->PacketType = HEADER_ZC_NPC_MARKET_PURCHASE_RESULT; p->PacketLength = sizeof( struct PACKET_ZC_NPC_MARKET_PURCHASE_RESULT ); #if PACKETVER_MAIN_NUM >= 20190807 || PACKETVER_RE_NUM >= 20190807 || PACKETVER_ZERO_NUM >= 20190814 p->result = ( res == e_purchase_result::PURCHASE_SUCCEED ? 0 : -1 ); #else p->result = ( res == e_purchase_result::PURCHASE_SUCCEED ? 1 : 0 ); #endif if( p->result ){ for( int i = 0, j, count = 0; i < list.size(); i++ ){ ARR_FIND( 0, nd->u.shop.count, j, list[i].nameid == nd->u.shop.shop_item[j].nameid ); // Not found if( j == nd->u.shop.count ){ continue; } p->list[count].ITID = client_nameid( list[i].nameid ); p->list[count].qty = list[i].qty; p->list[count].price = nd->u.shop.shop_item[j].value; p->PacketLength += sizeof( struct PACKET_ZC_NPC_MARKET_PURCHASE_RESULT_sub ); count++; } } clif_send( p, p->PacketLength, &sd->bl, SELF ); #endif } /// Purchase item from Market shop. /// 0x9d6 .W { .W .L } (CZ_NPC_MARKET_PURCHASE) void clif_parse_NPCMarketPurchase(int fd, struct map_session_data *sd) { #if PACKETVER >= 20131223 nullpo_retv( sd ); if( !sd->npc_shopid ){ return; } const struct PACKET_CZ_NPC_MARKET_PURCHASE* p = (struct PACKET_CZ_NPC_MARKET_PURCHASE*)RFIFOP( fd, 0 ); int count = ( p->PacketLength - sizeof( struct PACKET_CZ_NPC_MARKET_PURCHASE ) ) / sizeof( struct PACKET_CZ_NPC_MARKET_PURCHASE_sub ); std::vector items; items.reserve( count ); for( int i = 0; i < count; i++ ){ s_npc_buy_list item = {}; item.nameid = p->list[i].ITID; item.qty = p->list[i].qty; items.push_back( item ); } e_purchase_result res = npc_buylist( sd, items ); clif_npc_market_purchase_ack( sd, res, items ); #endif } /// Displays an NPC dialog message (ZC_SAY_DIALOG). /// 00b4 .W .L .?B /// Client behavior (dialog window): /// - disable mouse targeting /// - open the dialog window /// - set npcid of dialog window (0 by default) /// - if set to clear on next mes, clear contents /// - append this text void clif_scriptmes(struct map_session_data *sd, int npcid, const char *mes) { int fd = sd->fd; int slen = strlen(mes) + 9; WFIFOHEAD(fd, slen); WFIFOW(fd,0)=0xb4; WFIFOW(fd,2)=slen; WFIFOL(fd,4)=npcid; memcpy(WFIFOCP(fd,8), mes, slen-8); WFIFOSET(fd,WFIFOW(fd,2)); } /// Adds a 'next' button to an NPC dialog (ZC_WAIT_DIALOG). /// 00b5 .L /// Client behavior (dialog window): /// - disable mouse targeting /// - open the dialog window /// - add 'next' button /// When 'next' is pressed: /// - 00B9 .L /// - set to clear on next mes /// - remove 'next' button void clif_scriptnext(struct map_session_data *sd,int npcid) { int fd; nullpo_retv(sd); fd=sd->fd; WFIFOHEAD(fd, packet_len(0xb5)); WFIFOW(fd,0)=0xb5; WFIFOL(fd,2)=npcid; WFIFOSET(fd,packet_len(0xb5)); } /// Adds a 'close' button to an NPC dialog (ZC_CLOSE_DIALOG). /// 00b6 .L /// Client behavior: /// - if dialog window is open: /// - remove 'next' button /// - add 'close' button /// - else: /// - enable mouse targeting /// - close the dialog window /// - close the menu window /// When 'close' is pressed: /// - enable mouse targeting /// - close the dialog window /// - close the menu window /// - 0146 .L void clif_scriptclose(struct map_session_data *sd, int npcid) { int fd; nullpo_retv(sd); fd=sd->fd; WFIFOHEAD(fd, packet_len(0xb6)); WFIFOW(fd,0)=0xb6; WFIFOL(fd,2)=npcid; WFIFOSET(fd,packet_len(0xb6)); } /** * Close script when player is idle * 08d6 .L (ZC_CLEAR_DIALOG) * @author [Ind/Hercules] * @param sd : player pointer * @param npcid : npc gid to close */ void clif_scriptclear(struct map_session_data *sd, int npcid) { struct s_packet_db* info; int16 len; int cmd = 0; int fd; nullpo_retv(sd); cmd = packet_db_ack[ZC_CLEAR_DIALOG]; if(!cmd) cmd = 0x8d6; //default info = &packet_db[cmd]; len = info->len; fd = sd->fd; WFIFOHEAD(fd, len); WFIFOW(fd,0)=0x8d6; WFIFOL(fd,info->pos[0])=npcid; WFIFOSET(fd,len); } /*========================================== * *------------------------------------------*/ void clif_sendfakenpc(struct map_session_data *sd, int npcid) { unsigned char *buf; int fd = sd->fd; sd->state.using_fake_npc = 1; WFIFOHEAD(fd, packet_len(0x78)); buf = WFIFOP(fd,0); memset(WBUFP(buf,0), 0, packet_len(0x78)); WBUFW(buf,0)=0x78; #if PACKETVER >= 20071106 WBUFB(buf,2) = 0; // object type buf = WFIFOP(fd,1); #endif WBUFL(buf,2)=npcid; WBUFW(buf,14)=111; WBUFPOS(buf,46,sd->bl.x,sd->bl.y,sd->ud.dir); WBUFB(buf,49)=5; WBUFB(buf,50)=5; WFIFOSET(fd, packet_len(0x78)); } /// Displays an NPC dialog menu (ZC_MENU_LIST). /// 00b7 .W .L .?B /// Client behavior: /// - disable mouse targeting /// - close the menu window /// - open the menu window /// - add options to the menu (separated in the text by ":") /// - set npcid of menu window /// - if dialog window is open: /// - remove 'next' button /// When 'ok' is pressed: /// - 00B8 .L .B /// - close the menu window /// When 'cancel' is pressed: /// - 00B8 .L <-1>.B /// - enable mouse targeting /// - close a bunch of windows... /// WARNING: the 'cancel' button closes other windows besides the dialog window and the menu window. /// Which suggests their have intertwined behavior. (probably the mouse targeting) /// TODO investigate behavior of other windows [FlavioJS] void clif_scriptmenu(struct map_session_data* sd, int npcid, const char* mes) { int fd = sd->fd; int slen = strlen(mes) + 9; struct block_list *bl = NULL; if (!sd->state.using_fake_npc && (npcid == fake_nd->bl.id || ((bl = map_id2bl(npcid)) && (bl->m!=sd->bl.m || bl->xbl.x-AREA_SIZE-1 || bl->x>sd->bl.x+AREA_SIZE+1 || bl->ybl.y-AREA_SIZE-1 || bl->y>sd->bl.y+AREA_SIZE+1)))) clif_sendfakenpc(sd, npcid); WFIFOHEAD(fd, slen); WFIFOW(fd,0)=0xb7; WFIFOW(fd,2)=slen; WFIFOL(fd,4)=npcid; memcpy(WFIFOCP(fd,8), mes, slen-8); WFIFOSET(fd,WFIFOW(fd,2)); } /// Displays an NPC dialog input box for numbers (ZC_OPEN_EDITDLG). /// 0142 .L /// Client behavior (inputnum window): /// - if npcid exists in the client: /// - open the inputnum window /// - set npcid of inputnum window /// When 'ok' is pressed: /// - if inputnum window has text: /// - if npcid exists in the client: /// - 0143 .L .L /// - close inputnum window void clif_scriptinput(struct map_session_data *sd, int npcid) { int fd; struct block_list *bl = NULL; nullpo_retv(sd); if (!sd->state.using_fake_npc && (npcid == fake_nd->bl.id || ((bl = map_id2bl(npcid)) && (bl->m!=sd->bl.m || bl->xbl.x-AREA_SIZE-1 || bl->x>sd->bl.x+AREA_SIZE+1 || bl->ybl.y-AREA_SIZE-1 || bl->y>sd->bl.y+AREA_SIZE+1)))) clif_sendfakenpc(sd, npcid); fd=sd->fd; WFIFOHEAD(fd, packet_len(0x142)); WFIFOW(fd,0)=0x142; WFIFOL(fd,2)=npcid; WFIFOSET(fd,packet_len(0x142)); } /// Displays an NPC dialog input box for numbers (ZC_OPEN_EDITDLGSTR). /// 01d4 .L /// Client behavior (inputstr window): /// - if npcid is 0 or npcid exists in the client: /// - open the inputstr window /// - set npcid of inputstr window /// When 'ok' is pressed: /// - if inputstr window has text and isn't an insult(manner.txt): /// - if npcid is 0 or npcid exists in the client: /// - 01d5 .W .L .?B /// - close inputstr window void clif_scriptinputstr(struct map_session_data *sd, int npcid) { int fd; struct block_list *bl = NULL; nullpo_retv(sd); if (!sd->state.using_fake_npc && (npcid == fake_nd->bl.id || ((bl = map_id2bl(npcid)) && (bl->m!=sd->bl.m || bl->xbl.x-AREA_SIZE-1 || bl->x>sd->bl.x+AREA_SIZE+1 || bl->ybl.y-AREA_SIZE-1 || bl->y>sd->bl.y+AREA_SIZE+1)))) clif_sendfakenpc(sd, npcid); fd=sd->fd; WFIFOHEAD(fd, packet_len(0x1d4)); WFIFOW(fd,0)=0x1d4; WFIFOL(fd,2)=npcid; WFIFOSET(fd,packet_len(0x1d4)); } /// Marks a position on client's minimap (ZC_COMPASS). /// 0144 .L .L .L .L .B .L /// npc id: /// is ignored in the client /// type: /// 0 = display mark for 15 seconds /// 1 = display mark until dead or teleported /// 2 = remove mark /// color: /// 0x00RRGGBB void clif_viewpoint(struct map_session_data *sd, int npc_id, int type, int x, int y, int id, int color) { int fd; nullpo_retv(sd); fd=sd->fd; WFIFOHEAD(fd, packet_len(0x144)); WFIFOW(fd,0)=0x144; WFIFOL(fd,2)=npc_id; WFIFOL(fd,6)=type; WFIFOL(fd,10)=x; WFIFOL(fd,14)=y; WFIFOB(fd,18)=id; WFIFOL(fd,19)=color; WFIFOSET(fd,packet_len(0x144)); } /// Displays an illustration image. /// 0145 .16B .B (ZC_SHOW_IMAGE) /// 01b3 .64B .B (ZC_SHOW_IMAGE2) /// type: /// 0 = bottom left corner /// 1 = bottom middle /// 2 = bottom right corner /// 3 = middle of screen, inside a movable window /// 4 = middle of screen, movable with a close button, chrome-less /// 255 = clear all displayed cutins void clif_cutin(struct map_session_data* sd, const char* image, int type) { int fd; nullpo_retv(sd); fd=sd->fd; WFIFOHEAD(fd, packet_len(0x1b3)); WFIFOW(fd,0)=0x1b3; strncpy(WFIFOCP(fd,2),image,64); WFIFOB(fd,66)=type; WFIFOSET(fd,packet_len(0x1b3)); } /*========================================== * Fills in card data from the given item and into the buffer. [Skotlex] *------------------------------------------*/ static void clif_addcards( struct EQUIPSLOTINFO* buf, struct item* item ){ nullpo_retv( buf ); // Blank data if( item == nullptr ){ buf->card[0] = 0; buf->card[1] = 0; buf->card[2] = 0; buf->card[3] = 0; return; } // Pet eggs if( item->card[0] == CARD0_PET ){ buf->card[0] = 0; buf->card[1] = 0; // Pet intimacy // No idea when this was added exactly, but older clients have no problem if we send it anyway buf->card[2] = item->card[3] >> 1; // Pet renamed flag buf->card[3] = item->card[3] & 1; return; } // Forged/created items if( item->card[0] == CARD0_FORGE || item->card[0] == CARD0_CREATE ){ buf->card[0] = item->card[0]; buf->card[1] = item->card[1]; buf->card[2] = item->card[2]; buf->card[3] = item->card[3]; return; } int i = 0, j; // Client only receives four cards.. so randomly send them a set of cards. [Skotlex] if( MAX_SLOTS > 4 && ( j = itemdb_slots( item->nameid ) ) > 4 ){ i = rnd() % ( j - 3 ); //eg: 6 slots, possible i values: 0->3, 1->4, 2->5 => i = rnd()%3; } // Normal items. for( int k = 0; k < 4; k++, i++ ){ if( item->card[i] > 0 ){ buf->card[k] = client_nameid( item->card[i] ); }else{ buf->card[k] = 0; } } } /// Fills in part of the item buffers that calls for variable bonuses data. [Napster] /// A maximum of 5 random options can be supported. static uint8 clif_add_random_options( struct ItemOptions buf[MAX_ITEM_RDM_OPT], struct item* it ){ nullpo_retr( 0, it ); uint8 count = 0; for( int i = 0; i < MAX_ITEM_RDM_OPT; i++ ){ if( it->option[i].id ){ buf[i].index = it->option[i].id; // OptIndex buf[i].value = it->option[i].value; // Value buf[i].param = it->option[i].param; // Param1 count++; }else{ buf[i].index = 0; buf[i].value = 0; buf[i].param = 0; } } #if MAX_ITEM_RDM_OPT < 5 for( ; i < 5; i++ ){ buf[i].index = 0; // OptIndex buf[i].value = 0; // Value buf[i].param = 0; // Param1 } #endif return count; } /// Notifies the client, about a received inventory item or the result of a pick-up request. /// 00a0 .W .W .W .B .B .B .W .W .W .W .W .B .B (ZC_ITEM_PICKUP_ACK) /// 029a .W .W .W .B .B .B .W .W .W .W .W .B .B .L (ZC_ITEM_PICKUP_ACK2) /// 02d4 .W .W .W .B .B .B .W .W .W .W .W .B .B .L .W (ZC_ITEM_PICKUP_ACK3) /// 0990 .W .W .W .B .B .B .W .W .W .W .L .B .B .L .W (ZC_ITEM_PICKUP_ACK_V5) /// 0a0c .W .W .W .B .B .B .W .W .W .W .L .B .B .L .W {