// Copyright (c) rAthena Dev Teams - 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; } /* 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]; unsigned long color_table[COLOR_MAX]; #include "clif_obfuscation.hpp" static bool clif_session_isValid(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 /** 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(unsigned short 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; } 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) { 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: return pcdb_checkid(status_get_viewdata(bl)->class_)?0x0:0x5; //NPC_MOB_TYPE case BL_NPC: #if PACKETVER >= 20170726 return 0x6; //NPC_EVT_TYPE #else return (pcdb_checkid(status_get_viewdata(bl)->class_) ? 0x0 : 0x6); #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 != NULL && 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); fd = sd->fd; if (!fd) //Don't send to disconnected clients. 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; } if (session[fd] == NULL) return 0; /* 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 uint8* 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 = (TBL_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 = (TBL_PC*)mapit_next(iter)) != NULL ) { if( 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: 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 ((fd=cd->usersd[i]->fd) >0 && 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: 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( !(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 = (TBL_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 = (TBL_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)) { 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) != 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 (!enable_spy) //Skip unnecessary parsing. [Skotlex] break; iter = mapit_getallusers(); while( (tsd = (TBL_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: 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 && (bg = util::umap_find(bg_team_db, sd->bg_id))) { for( i = 0; i < bg->members.size(); i++ ) { if( (sd = bg->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 CLAN: if( sd && sd->clan ){ struct clan* clan = sd->clan; for( i = 0; i < clan->max_member; i++ ){ if( ( sd = clan->members[i] ) == NULL || !(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 = (TBL_PC*)mapit_next(iter)) != NULL){ if (tsd->clanspy == clan->id){ WFIFOHEAD(tsd->fd, len); memcpy(WFIFOP(tsd->fd, 0), buf, len); WFIFOSET(tsd->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 (!fd || !session[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 || !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) { #if PACKETVER >= 20180418 uint8 buf[22]; uint32 header = 0xadd; #elif PACKETVER >= 20130000 uint8 buf[19]; uint32 header=0x84b; #else uint8 buf[17]; uint32 header=0x09e; #endif int view, offset=0; nullpo_retv(fitem); if (fitem->item.nameid == 0) return; WBUFW(buf, offset+0) = header; WBUFL(buf, offset+2) = fitem->bl.id; WBUFW(buf, offset+6) = ((view = itemdb_viewid(fitem->item.nameid)) > 0) ? view : fitem->item.nameid; #if PACKETVER >= 20130000 WBUFW(buf, offset+8) = itemtype(fitem->item.nameid); offset +=2; #endif WBUFB(buf, offset+8) = fitem->item.identify; WBUFW(buf, offset+9) = fitem->bl.x; WBUFW(buf, offset+11) = fitem->bl.y; WBUFB(buf, offset+13) = fitem->subx; WBUFB(buf, offset+14) = fitem->suby; WBUFW(buf, offset+15) = fitem->item.amount; #if PACKETVER >= 20180418 if( canShowEffect ){ uint8 dropEffect = itemdb_dropeffect(fitem->item.nameid); if( dropEffect > 0 ){ WBUFB(buf, offset+17) = 1; WBUFW(buf, offset+18) = dropEffect - 1; }else{ WBUFB(buf, offset+17) = 0; WBUFW(buf, offset+18) = 0; } }else{ WBUFB(buf, offset+17) = 0; WBUFW(buf, offset+18) = 0; } #endif clif_send(buf, packet_len(header), &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 (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 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) = -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, unsigned short *rhand, unsigned short *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 int clif_set_unit_idle(struct block_list* bl, unsigned char* buffer, bool spawn, bool option, unsigned int option_val) { struct map_session_data* sd; struct status_change* sc = status_get_sc(bl); struct view_data* vd = status_get_viewdata(bl); unsigned char *buf = WBUFP(buffer, 0); #if PACKETVER < 20091103 bool type = !pcdb_checkid(vd->class_); #endif unsigned short offset = 0; #if PACKETVER >= 20091103 const char *name; #endif sd = BL_CAST(BL_PC, bl); if (!option) option_val = ((sc) ? sc->option : 0); #if PACKETVER < 20091103 if(type) WBUFW(buf,0) = spawn ? 0x7c : 0x78; else #endif #if PACKETVER < 4 WBUFW(buf,0) = spawn ? 0x79 : 0x78; #elif PACKETVER < 7 WBUFW(buf,0) = spawn ? 0x1d9 : 0x1d8; #elif PACKETVER < 20080102 WBUFW(buf,0) = spawn ? 0x22b : 0x22a; #elif PACKETVER < 20091103 WBUFW(buf,0) = spawn ? 0x2ed : 0x2ee; #elif PACKETVER < 20101124 WBUFW(buf,0) = spawn ? 0x7f8 : 0x7f9; #elif PACKETVER < 20120221 WBUFW(buf,0) = spawn ? 0x858 : 0x857; #elif PACKETVER < 20131223 WBUFW(buf,0) = spawn ? 0x90f : 0x915; #elif PACKETVER < 20150513 WBUFW(buf,0) = spawn ? 0x9dc : 0x9dd; #else WBUFW(buf,0) = spawn ? 0x9fe : 0x9ff; #endif #if PACKETVER >= 20091103 name = status_get_name(bl); #if PACKETVER < 20110111 WBUFW(buf,2) = (uint16)((spawn ? 62 : 63)+strlen(name)); #elif PACKETVER < 20120221 WBUFW(buf,2) = (uint16)((spawn ? 64 : 65)+strlen(name)); #elif PACKETVER < 20130807 WBUFW(buf,2) = (uint16)((spawn ? 77 : 78)+strlen(name)); #else WBUFW(buf,2) = (uint16)((spawn ? 79 : 80)+strlen(name)); #endif WBUFB(buf,4) = clif_bl_type(bl); offset+=3; buf = WBUFP(buffer,offset); #elif PACKETVER >= 20071106 if (type) { //Non-player packets WBUFB(buf,2) = clif_bl_type(bl); offset++; buf = WBUFP(buffer,offset); } #endif WBUFL(buf, 2) = bl->id; #if PACKETVER >= 20131223 WBUFL(buf,6) = (sd) ? sd->status.char_id : 0; // GID/CCODE offset+=4; buf = WBUFP(buffer,offset); #endif WBUFW(buf, 6) = status_get_speed(bl); WBUFW(buf, 8) = (sc)? sc->opt1 : 0; WBUFW(buf,10) = (sc)? sc->opt2 : 0; #if PACKETVER < 20091103 if (type&&spawn) { //uses an older and different packet structure WBUFW(buf,12) = option_val; WBUFW(buf,14) = vd->hair_style; WBUFW(buf,16) = vd->weapon; WBUFW(buf,18) = vd->head_bottom; WBUFW(buf,20) = vd->class_; //Pet armor (ignored by client) WBUFW(buf,22) = vd->shield; } else { #endif #if PACKETVER >= 20091103 WBUFL(buf,12) = option_val; offset+=2; buf = WBUFP(buffer,offset); #elif PACKETVER >= 7 if (!type) { WBUFL(buf,12) = option_val; offset+=2; buf = WBUFP(buffer,offset); } else WBUFW(buf,12) = option_val; #else WBUFW(buf,12) = option_val; #endif WBUFW(buf,14) = vd->class_; WBUFW(buf,16) = vd->hair_style; WBUFW(buf,18) = vd->weapon; #if PACKETVER < 4 WBUFW(buf,20) = vd->head_bottom; WBUFW(buf,22) = vd->shield; #else WBUFW(buf,20) = vd->shield; WBUFW(buf,22) = vd->head_bottom; #endif #if PACKETVER < 20091103 } #endif WBUFW(buf,24) = vd->head_top; WBUFW(buf,26) = vd->head_mid; if( bl->type == BL_NPC && vd->class_ == JT_GUILD_FLAG ) { //The hell, why flags work like this? WBUFW(buf,22) = status_get_emblem_id(bl); WBUFW(buf,24) = GetWord(status_get_guild_id(bl), 1); WBUFW(buf,26) = GetWord(status_get_guild_id(bl), 0); } WBUFW(buf,28) = vd->hair_color; WBUFW(buf,30) = vd->cloth_color; WBUFW(buf,32) = (sd)? sd->head_dir : 0; #if PACKETVER < 20091103 if (type&&spawn) { //End of packet 0x7c WBUFB(buf,34) = (sd) ? sd->status.karma : 0; // karma WBUFB(buf,35) = vd->sex; WBUFPOS(buf,36, bl->x, bl->y, unit_getdir(bl)); WBUFB(buf,39) = 0; WBUFB(buf,40) = 0; return packet_len(0x7c); } #endif #if PACKETVER >= 20110111 WBUFW(buf,34) = vd->robe; offset+= 2; buf = WBUFP(buffer,offset); #endif WBUFL(buf,34) = status_get_guild_id(bl); WBUFW(buf,38) = status_get_emblem_id(bl); WBUFW(buf,40) = (sd)? sd->status.manner : 0; #if PACKETVER >= 20091103 WBUFL(buf,42) = (sc)? sc->opt3 : 0; offset+=2; buf = WBUFP(buffer,offset); #elif PACKETVER >= 7 if (!type) { WBUFL(buf,42) = (sc)? sc->opt3 : 0; offset+=2; buf = WBUFP(buffer,offset); } else WBUFW(buf,42) = (sc)? sc->opt3 : 0; #else WBUFW(buf,42) = (sc)? sc->opt3 : 0; #endif WBUFB(buf,44) = (sd)? sd->status.karma : 0; WBUFB(buf,45) = vd->sex; WBUFPOS(buf,46,bl->x,bl->y,unit_getdir(bl)); WBUFB(buf,49) = (sd)? 5 : 0; WBUFB(buf,50) = (sd)? 5 : 0; if (!spawn) { WBUFB(buf,51) = vd->dead_sit; offset++; buf = WBUFP(buffer,offset); } WBUFW(buf,51) = clif_setlevel(bl); #if PACKETVER < 20091103 if (type) //End for non-player packet return packet_len(WBUFW(buffer,0)); #endif #if PACKETVER >= 20080102 WBUFW(buf,53) = (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)) ) { WBUFL(buf,55) = status_get_max_hp(bl); // maxHP WBUFL(buf,59) = status_get_hp(bl); // HP } else { WBUFL(buf,55) = -1; // maxHP WBUFL(buf,59) = -1; // HP } WBUFB(buf,63) = ( bl->type == BL_MOB && (((TBL_MOB*)bl)->db->mexp > 0) ) ? 1 : 0; // isBoss #endif #if PACKETVER >= 20150513 WBUFW(buf,64) = vd->body_style; // body offset+= 2; buf = WBUFP(buffer,offset); #endif #if PACKETVER >= 20091103 #if PACKETVER >= 20120221 safestrncpy(WBUFCP(buf,64), name, NAME_LENGTH); #else safestrncpy(WBUFCP(buf,55), name, NAME_LENGTH); #endif return WBUFW(buffer,2); #else return packet_len(WBUFW(buffer,0)); #endif } /*========================================== * Prepares 'unit walking' packet *------------------------------------------*/ static int clif_set_unit_walking(struct block_list* bl, struct unit_data* ud, unsigned char* buffer) { struct map_session_data* sd; struct status_change* sc = status_get_sc(bl); struct view_data* vd = status_get_viewdata(bl); unsigned char* buf = WBUFP(buffer,0); #if PACKETVER >= 7 unsigned short offset = 0; #endif #if PACKETVER >= 20091103 const char *name; #endif sd = BL_CAST(BL_PC, bl); #if PACKETVER < 4 WBUFW(buf, 0) = 0x7b; #elif PACKETVER < 7 WBUFW(buf, 0) = 0x1da; #elif PACKETVER < 20080102 WBUFW(buf, 0) = 0x22c; #elif PACKETVER < 20091103 WBUFW(buf, 0) = 0x2ec; #elif PACKETVER < 20101124 WBUFW(buf, 0) = 0x7f7; #elif PACKETVER < 20120221 WBUFW(buf, 0) = 0x856; #elif PACKETVER < 20131223 WBUFW(buf, 0) = 0x914; #elif PACKETVER < 20150513 WBUFW(buf, 0) = 0x9db; #else WBUFW(buf, 0) = 0x9fd; #endif #if PACKETVER >= 20091103 name = status_get_name(bl); #if PACKETVER < 20110111 WBUFW(buf, 2) = (uint16)(69+strlen(name)); #elif PACKETVER < 20120221 WBUFW(buf, 2) = (uint16)(71+strlen(name)); #elif PACKETVER < 20130807 WBUFW(buf, 2) = (uint16)(84+strlen(name)); #else WBUFW(buf, 2) = (uint16)(86+strlen(name)); #endif offset+=2; buf = WBUFP(buffer,offset); #endif #if PACKETVER >= 20071106 WBUFB(buf, 2) = clif_bl_type(bl); offset++; buf = WBUFP(buffer,offset); #endif WBUFL(buf, 2) = bl->id; #if PACKETVER >= 20131223 WBUFL(buf,6) = (sd) ? sd->status.char_id : 0; // GID/CCODE offset+=4; buf = WBUFP(buffer,offset); #endif WBUFW(buf, 6) = status_get_speed(bl); WBUFW(buf, 8) = (sc)? sc->opt1 : 0; WBUFW(buf,10) = (sc)? sc->opt2 : 0; #if PACKETVER < 7 WBUFW(buf,12) = (sc)? sc->option : 0; #else WBUFL(buf,12) = (sc)? sc->option : 0; offset+=2; //Shift the rest of elements by 2 bytes. buf = WBUFP(buffer,offset); #endif WBUFW(buf,14) = vd->class_; WBUFW(buf,16) = vd->hair_style; WBUFW(buf,18) = vd->weapon; #if PACKETVER < 4 WBUFW(buf,20) = vd->head_bottom; WBUFL(buf,22) = client_tick(gettick()); WBUFW(buf,26) = vd->shield; #else WBUFW(buf,20) = vd->shield; WBUFW(buf,22) = vd->head_bottom; WBUFL(buf,24) = client_tick(gettick()); #endif WBUFW(buf,28) = vd->head_top; WBUFW(buf,30) = vd->head_mid; WBUFW(buf,32) = vd->hair_color; WBUFW(buf,34) = vd->cloth_color; WBUFW(buf,36) = (sd)? sd->head_dir : 0; #if PACKETVER >= 20110111 WBUFW(buf,38) = vd->robe; offset+= 2; buf = WBUFP(buffer,offset); #endif WBUFL(buf,38) = status_get_guild_id(bl); WBUFW(buf,42) = status_get_emblem_id(bl); WBUFW(buf,44) = (sd)? sd->status.manner : 0; #if PACKETVER < 7 WBUFW(buf,46) = (sc)? sc->opt3 : 0; #else WBUFL(buf,46) = (sc)? sc->opt3 : 0; offset+=2; //Shift the rest of elements by 2 bytes. buf = WBUFP(buffer,offset); #endif WBUFB(buf,48) = (sd)? sd->status.karma : 0; WBUFB(buf,49) = vd->sex; WBUFPOS2(buf,50,bl->x,bl->y,ud->to_x,ud->to_y,8,8); WBUFB(buf,56) = (sd)? 5 : 0; WBUFB(buf,57) = (sd)? 5 : 0; WBUFW(buf,58) = clif_setlevel(bl); #if PACKETVER >= 20080102 WBUFW(buf,60) = (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)) ) { WBUFL(buf,62) = status_get_max_hp(bl); // maxHP WBUFL(buf,66) = status_get_hp(bl); // HP } else { WBUFL(buf,62) = -1; // maxHP WBUFL(buf,66) = -1; // HP } WBUFB(buf,70) = ( bl->type == BL_MOB && (((TBL_MOB*)bl)->db->mexp > 0) ) ? 1 : 0; // isBoss #endif #if PACKETVER >= 20150513 WBUFW(buf,71) = vd->body_style; // body offset+= 2; buf = WBUFP(buffer,offset); #endif #if PACKETVER >= 20091103 #if PACKETVER >= 20120221 safestrncpy(WBUFCP(buf,71), name, NAME_LENGTH); #else safestrncpy(WBUFCP(buf,62), name, NAME_LENGTH); #endif return WBUFW(buffer,2); #else return packet_len(WBUFW(buffer,0)); #endif } //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); } } /// 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) { 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_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)); } /// Notifies the client of an object's souls. /// Note: Spirit spheres and Soul spheres work on /// seprate systems officially, but both send out /// the same packet which leads to confusion on how /// much soul energy a Soul Reaper acturally has /// should the player also have spirit spheres. /// They will likely create a new packet for this soon /// to seprate the animations for spirit and soul spheres. /// For now well use this and replace it later when possible. [Rytech] /// /// 01d0 .L .W (ZC_SPIRITS) /// 01e1 .L .W (ZC_SPIRITS2) static void clif_soulball_single(int fd, struct map_session_data *sd) { WFIFOHEAD(fd, packet_len(0x1d0)); WFIFOW(fd,0)=0x1d0; WFIFOL(fd,2)=sd->bl.id; WFIFOW(fd,6)=sd->soulball; WFIFOSET(fd, packet_len(0x1d0)); } /*========================================== * 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); } /** * Main function to spawn a unit on the client (player/mob/pet/etc) **/ int clif_spawn(struct block_list *bl) { unsigned char buf[128]; struct view_data *vd; int len; vd = status_get_viewdata(bl); if( !vd || vd->class_ == JT_INVISIBLE ) return 0; /** * Hide NPC from maya purple card. **/ if(bl->type == BL_NPC && !((TBL_NPC*)bl)->chat_id && (((TBL_NPC*)bl)->sc.option&OPTION_INVISIBLE)) return 0; len = clif_set_unit_idle(bl, buf, (bl->type == BL_NPC && vd->dead_sit ? false : true), false, 0); clif_send(buf, len, bl, AREA_WOS); if (disguised(bl)) clif_setdisguise(bl, buf, len); 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->soulball > 0) clif_soulball(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); } 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) { struct status_data *status; unsigned char buf[128]; int offset; #if PACKETVER < 20141016 const int cmd = 0x22e; #else const int cmd = 0x9f7; #endif int htype; nullpo_retv(hd); if (!clif_session_isValid(sd)) return; status = &hd->battle_status; htype = hom_class2type(hd->homunculus.class_); memset(buf,0,packet_len(cmd)); WBUFW(buf,0) = cmd; safestrncpy(WBUFCP(buf,2), 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) WBUFB(buf,26) = (battle_config.hom_rename ? 0 : hd->homunculus.rename_flag) | (hd->homunculus.vaporize << 1) | (hd->homunculus.hp ? 0 : 4); WBUFW(buf,27) = hd->homunculus.level; WBUFW(buf,29) = hd->homunculus.hunger; WBUFW(buf,31) = (unsigned short) (hd->homunculus.intimacy / 100) ; WBUFW(buf,33) = 0; // equip id WBUFW(buf,35) = cap_value(status->rhw.atk2 + status->batk, 0, INT16_MAX); WBUFW(buf,37)=i16min(status->matk_max, INT16_MAX); //FIXME capping to INT16 here is too late WBUFW(buf,39)=status->hit; if (battle_config.hom_setting&HOMSET_DISPLAY_LUK) WBUFW(buf,41) = status->luk/3 + 1; //crit is a +1 decimal value! Just display purpose.[Vicious] else WBUFW(buf,41) = status->cri/10; #ifdef RENEWAL WBUFW(buf,43) = status->def + status->def2; WBUFW(buf,45) = status->mdef + status->mdef2; #else WBUFW(buf,43) = status->def + status->vit; WBUFW(buf,45) = status->mdef; #endif WBUFW(buf,47) = status->flee; WBUFW(buf,49) = (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)) { WBUFL(buf,51) = status->hp/(status->max_hp/100); WBUFL(buf,55) = 100; } else { WBUFL(buf,51) = status->hp; WBUFL(buf,55) = status->max_hp; } offset = 4; #else if (status->max_hp > INT16_MAX) { WBUFW(buf,51) = status->hp/(status->max_hp/100); WBUFW(buf,53) = 100; } else { WBUFW(buf,51) = status->hp; WBUFW(buf,53) = status->max_hp; } offset = 0; #endif if (status->max_sp > INT16_MAX) { WBUFW(buf,55+offset) = status->sp/(status->max_sp/100); WBUFW(buf,57+offset) = 100; } else { WBUFW(buf,55+offset) = status->sp; WBUFW(buf,57+offset) = status->max_sp; } WBUFL(buf,59+offset) = hd->homunculus.exp; WBUFL(buf,63+offset) = hd->exp_next; switch( htype ) { case HT_REG: case HT_EVO: if( hd->homunculus.level >= battle_config.hom_max_level ) WBUFL(buf,63+offset) = 0; break; case HT_S: if( hd->homunculus.level >= battle_config.hom_S_max_level ) WBUFL(buf,63+offset) = 0; break; } WBUFW(buf,67+offset) = hd->homunculus.skillpts; WBUFW(buf,69+offset) = status_get_range(&hd->bl); clif_send(buf,packet_len(cmd),&sd->bl,SELF); } /// 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)); } int clif_hom_food(struct map_session_data *sd,int foodid,int fail) //[orn] { int fd=sd->fd; WFIFOHEAD(fd,packet_len(0x22f)); WFIFOW(fd,0)=0x22f; WFIFOB(fd,2)=fail; WFIFOW(fd,3)=foodid; WFIFOSET(fd,packet_len(0x22f)); return 0; } /// 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) { uint8 buf[128]; int len; 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; len = clif_set_unit_walking(bl,ud,buf); clif_send(buf,len,bl,AREA_WOS); if (disguised(bl)) clif_setdisguise(bl, buf, len); 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)=-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(bl->type == BL_NPC && !((TBL_NPC*)bl)->chat_id && (((TBL_NPC*)bl)->sc.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; } 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)=-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 (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) { int fd; nullpo_retv(sd); 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) = -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 (ZC_PC_PURCHASE_ITEMLIST). /// 00c6 .W { .L .L .B .W }* void clif_buylist(struct map_session_data *sd, struct npc_data *nd) { int fd,i,c; bool discount; nullpo_retv(sd); nullpo_retv(nd); fd = sd->fd; WFIFOHEAD(fd, 4 + nd->u.shop.count * 11); WFIFOW(fd,0) = 0xc6; c = 0; discount = npc_shop_discount(nd); for( i = 0; i < nd->u.shop.count; i++ ) { struct item_data* id = itemdb_exists(nd->u.shop.shop_item[i].nameid); int val = nd->u.shop.shop_item[i].value; if( id == NULL ) continue; WFIFOL(fd, 4+c*11) = val; WFIFOL(fd, 8+c*11) = (discount) ? pc_modifybuyvalue(sd,val) : val; WFIFOB(fd,12+c*11) = itemtype(id->nameid); WFIFOW(fd,13+c*11) = ( id->view_id > 0 ) ? id->view_id : id->nameid; c++; } WFIFOW(fd,2) = 4 + c*11; WFIFOSET(fd,WFIFOW(fd,2)); } /// 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; 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 **/ void clif_npc_market_open(struct map_session_data *sd, struct npc_data *nd) { #if PACKETVER >= 20131223 struct npc_item_list *shop = nd->u.shop.shop_item; unsigned short shop_size = nd->u.shop.count, i, c, cmd = 0x9d5; struct item_data *id = NULL; struct s_packet_db *info; int fd; nullpo_retv(sd); if (sd->state.trading) return; info = &packet_db[cmd]; if (!info || info->len == 0) return; fd = sd->fd; WFIFOHEAD(fd, 4 + shop_size * 13); WFIFOW(fd,0) = cmd; for (i = 0, c = 0; i < shop_size; i++) { if (shop[i].nameid && (id = itemdb_exists(shop[i].nameid))) { WFIFOW(fd, 4+c*13) = shop[i].nameid; WFIFOB(fd, 6+c*13) = itemtype(id->nameid); WFIFOL(fd, 7+c*13) = shop[i].value; WFIFOL(fd,11+c*13) = shop[i].qty; WFIFOW(fd,15+c*13) = id->weight; c++; } } WFIFOW(fd,2) = 4 + c*13; WFIFOSET(fd,WFIFOW(fd,2)); 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. void clif_npc_market_purchase_ack(struct map_session_data *sd, uint8 res, uint8 n, struct s_npc_buy_list *list) { #if PACKETVER >= 20131223 unsigned short cmd = 0x9d7, len = 0; struct npc_data* nd; uint8 result = (res == 0 ? 1 : 0); int fd = 0; struct s_packet_db *info; nullpo_retv(sd); nullpo_retv((nd = map_id2nd(sd->npc_shopid))); info = &packet_db[cmd]; if (!info || info->len == 0) return; fd = sd->fd; len = 5 + 8*n; WFIFOHEAD(fd, len); WFIFOW(fd, 0) = cmd; WFIFOW(fd, 2) = len; if (result) { uint8 i, j; struct npc_item_list *shop = nd->u.shop.shop_item; unsigned short count = nd->u.shop.count; for (i = 0; i < n; i++) { WFIFOW(fd, 5+i*8) = list[i].nameid; WFIFOW(fd, 7+i*8) = list[i].qty; ARR_FIND(0, count, j, list[i].nameid == shop[j].nameid); WFIFOL(fd, 9+i*8) = (j != count) ? shop[j].value : 0; } } WFIFOB(fd, 4) = n; WFIFOSET(fd, len); #endif } /// Purchase item from Market shop. void clif_parse_NPCMarketPurchase(int fd, struct map_session_data *sd) { #if PACKETVER >= 20131223 struct s_packet_db* info; struct s_npc_buy_list *item_list; uint16 len = 0, i = 0; uint8 res = 0, n = 0; nullpo_retv(sd); if (!sd->npc_shopid) return; info = &packet_db[RFIFOW(fd,0)]; if (!info || info->len == 0) return; len = RFIFOW(fd,info->pos[0]); n = (len-4) / 6; CREATE(item_list, struct s_npc_buy_list, n); for (i = 0; i < n; i++) { item_list[i].nameid = RFIFOW(fd,info->pos[1]+i*6); item_list[i].qty = (uint16)min(RFIFOL(fd,info->pos[2]+i*6),USHRT_MAX); } res = npc_buylist(sd, n, item_list); clif_npc_market_purchase_ack(sd, res, n, item_list); aFree(item_list); #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(unsigned char* buf, struct item* item) { int i=0,j; if( item == NULL ) { //Blank data WBUFW(buf,0) = 0; WBUFW(buf,2) = 0; WBUFW(buf,4) = 0; WBUFW(buf,6) = 0; return; } if( item->card[0] == CARD0_PET ) { //pet eggs WBUFW(buf,0) = 0; WBUFW(buf,2) = 0; WBUFW(buf,4) = 0; WBUFW(buf,6) = item->card[3]; //Pet renamed flag. return; } if( item->card[0] == CARD0_FORGE || item->card[0] == CARD0_CREATE ) { //Forged/created items WBUFW(buf,0) = item->card[0]; WBUFW(buf,2) = item->card[1]; WBUFW(buf,4) = item->card[2]; WBUFW(buf,6) = item->card[3]; return; } //Client only receives four cards.. so randomly send them a set of cards. [Skotlex] if( MAX_SLOTS > 4 && (j = itemdb_slot(item->nameid)) > 4 ) i = rnd()%(j-3); //eg: 6 slots, possible i values: 0->3, 1->4, 2->5 => i = rnd()%3; //Normal items. if( item->card[i] > 0 && (j=itemdb_viewid(item->card[i])) > 0 ) WBUFW(buf,0) = j; else WBUFW(buf,0) = item->card[i]; if( item->card[++i] > 0 && (j=itemdb_viewid(item->card[i])) > 0 ) WBUFW(buf,2) = j; else WBUFW(buf,2) = item->card[i]; if( item->card[++i] > 0 && (j=itemdb_viewid(item->card[i])) > 0 ) WBUFW(buf,4) = j; else WBUFW(buf,4) = item->card[i]; if( item->card[++i] > 0 && (j=itemdb_viewid(item->card[i])) > 0 ) WBUFW(buf,6) = j; else WBUFW(buf,6) = item->card[i]; } /// Fills in part of the item buffers that calls for variable bonuses data. [Napster] /// A maximum of 5 random options can be supported. void clif_add_random_options(unsigned char* buf, struct item *it) { #if PACKETVER >= 20150225 int i; for (i = 0; i < MAX_ITEM_RDM_OPT; i++) { WBUFW(buf, i*5 + 0) = it->option[i].id; // OptIndex WBUFW(buf, i*5 + 2) = it->option[i].value; // Value WBUFB(buf, i*5 + 4) = it->option[i].param; // Param1 } #if MAX_ITEM_RDM_OPT < 5 for ( ; i < 5; i++) { WBUFW(buf, i*5 + 0) = 0; // OptIndex WBUFW(buf, i*5 + 2) = 0; // Value WBUFB(buf, i*5 + 4) = 0; // Param1 } #endif #endif } /// 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 {