// from Paul_T bool CWorldServer::SendPM( CPlayer* thisclient, char* Format, ... ) { char buf[512]; va_list ap; va_start( ap, Format ); vsprintf( buf, Format, ap ); BEGINPACKET( pak, 0x0784 ); ADDSTRING( pak, "Server" ); ADDBYTE( pak, 0 ); ADDSTRING( pak, buf ); ADDBYTE( pak, 0 ); thisclient->client->SendPacket(&pak); va_end( ap ); return true; }
// NPC Shout to the current map bool CWorldServer::NPCShout( CMonster* thismon, char* msg, char* npc,int mapid) { BEGINPACKET(pak, 0x0785); ADDSTRING ( pak, npc ); ADDBYTE ( pak, 0 ); ADDSTRING ( pak, msg ); ADDBYTE ( pak, 0 ); if(mapid!=0) SendToMap ( &pak, mapid ); else SendToMap ( &pak, thismon->Position->Map ); return true; }
bool CCharServer::pakDownloadCM ( CCharClient* thisclient, CPacket* P ) { unsigned int clanid = GETDWORD((*P), 0 ); MYSQL_RES* result = DB->QStore("SELECT logo FROM list_clan WHERE id=%i",clanid ); if(result==NULL) return true; if(mysql_num_rows(result)!=1) return true; MYSQL_ROW row = mysql_fetch_row(result); unsigned int cmid = atoi(row[0]); DB->QFree( ); char filename[30]; sprintf( filename, "clanmark/%u.cm", cmid ); FILE* fh = fopen( filename, "rb" ); if(fh==NULL) { Log( MSG_WARNING, "Invalid clanmark ID %i", cmid ); return true; } CClans* thisclan = GetClanByID(thisclient->clanid); BEGINPACKET( pak, 0x7e7 ); ADDDWORD ( pak, clanid ); while(!feof(fh)) { unsigned char charvalue = '\0'; fread( &charvalue, 1 , 1, fh ); ADDBYTE( pak, charvalue ); } ADDWORD( pak,0); thisclient->SendPacket( &pak ); fclose(fh); return true; }
//add / remove a Fairy void CWorldServer::DoFairyStuff(CPlayer* targetclient, int action) { BEGINPACKET( pak, 0x7dc ); ADDBYTE( pak, action ); ADDWORD( pak, targetclient->clientid); SendToVisible( &pak, targetclient ); }
// Return to char select bool CCharServer::pakWSCharSelect ( CCharClient* thisclient, CPacket* P ) { if(!thisclient->isLoggedIn) return false; Log( MSG_INFO, "World server requested char select" ); DWORD userid = GETDWORD( (*P), 0 ); CCharClient* otherclient = GetClientByUserID( userid ); if(otherclient==NULL) { Log(MSG_WARNING, "Invalid userid: %i", userid ); return true; } BEGINPACKET( pak, 0x71c ); ADDBYTE ( pak, 0x00 ); otherclient->SendPacket( &pak ); Log( MSG_INFO, "Client returning to char select" ); RESETPACKET( pak, 0x505 ); ADDDWORD( pak, thisclient->userid ); //cryptPacket( (char*)&pak, NULL ); CChanels* thischannel = GetChannelByID( thisclient->channel ); if(thischannel!=NULL) { send( thischannel->sock, (char*)&pak, pak.Size, 0 ); } //LMA: resetting chatroom ID. DisconnectClientFromChat(thisclient); return true; }
// Disconnect client from char/world server bool CCharServer::pakLoginDSClient( CCharClient* thisclient, CPacket* P ) { unsigned int userid = GETDWORD((*P), 1 ); CCharClient* otherclient = GetClientByUserID( userid ); BYTE action = GETBYTE((*P),0); switch(action) { case 1: { if(otherclient==NULL) { Log( MSG_WARNING, "Userid '%u' is not online", userid ); return true; } otherclient->isLoggedIn = false; otherclient->isActive = false; BEGINPACKET( pak, 0x502 ); ADDBYTE ( pak, 1 ); ADDDWORD ( pak, userid ); //ADDBYTE ( pak, 0x00 ); //cryptPacket( (char*)&pak, NULL ); CChanels* thischannel = GetChannelByID( otherclient->channel ); if(thischannel!=NULL) send( thischannel->sock, (char*)&pak, pak.Size, 0 ); } break; } return true; }
// Send server IP bool CLoginServer::pakGetIP( CLoginClient* thisclient, CPacket* P ) { if (!thisclient->isLoggedIn) return false; ; MYSQL_ROW row; DWORD servernum = GETDWORD( (*P), 0 ); BYTE channelnum = GETBYTE( (*P), 4 ); BEGINPACKET( pak, 0x70a ); if(!DB->QExecute( "UPDATE accounts SET lastsvr=%i,lastip='%s',lasttime=UNIX_TIMESTAMP( NOW() ) WHERE id=%i", channelnum, inet_ntoa( thisclient->clientinfo.sin_addr ), thisclient->userid)) return false; MYSQL_RES *result = DB->QStore( "SELECT host,port,lanip,lansubmask,connected,maxconnections FROM channels WHERE id=%i and type=1", servernum ); if(result==NULL) return false; if(mysql_num_rows(result)!=1) { Log(MSG_WARNING, "Player selected a invalid channel or channel offline" ); DB->QFree( ); return true; } row = mysql_fetch_row(result); UINT connected = atoi(row[4]); UINT maxconnections = atoi(row[5]); ADDBYTE( pak, 0 ); ADDDWORD( pak, thisclient->userid ); ADDDWORD( pak, 0x87654321 ); if(strcmp(thisclient->ClientSubNet,row[3])==0)//from lan { ADDSTRING( pak, row[2] ); Log(MSG_INFO, "Lan: %s choice channel #%i", thisclient->username.c_str(), channelnum); } else if(strcmp( thisclient->ClientSubNet ,"127.0.0")==0)//same computer { ADDSTRING( pak, "127.0.0.1" ); Log(MSG_INFO, "Server: %s choice channel #%i", thisclient->username.c_str(), channelnum); } else { ADDSTRING( pak, row[0] ); Log(MSG_INFO, "Inet: %s choice channel #%i", thisclient->username.c_str(), channelnum); } ADDBYTE( pak, 0 ); ADDWORD( pak, atoi(row[1]) ); DB->QFree( ); thisclient->SendPacket ( &pak ); return true; }
// Send Server encryption bool CLoginServer::pakEncryptionRequest( CLoginClient* thisclient, CPacket* P ) { BEGINPACKET( pak, 0x7ff ); ADDWORD ( pak, 0xaf02 ); ADDWORD ( pak, 0xbd46 ); ADDWORD ( pak, 0x0009 ); ADDWORD ( pak, 0x0012 ); ADDBYTE ( pak, 0x0000 ); ADDDWORD ( pak, 0xcdcdcdcd ); ADDDWORD ( pak, 0xcdcdcdcd ); ADDDWORD ( pak, 0xcdcdcdcd ); ADDDWORD ( pak, 0xcdcdcdcd ); ADDWORD ( pak, 0xcdcd ); ADDBYTE ( pak, 0xd3 ); thisclient->SendPacket( &pak ); Log(MSG_DEBUG, "Sent a 0x7ff packet to client"); return true; }
// Change player status in clan ((channel) = online/ 0xff = offline) bool CCharServer::ChangeClanStatus (CCharClient* thisclient, CCharClient* otherclient, int channel) { if (thisclient->charid !=otherclient->charid) { BEGINPACKET( pak, 0x7e0 ); ADDBYTE ( pak, 0x73 );//Change player status ADDBYTE ( pak, 0x00 ); ADDBYTE ( pak, channel );//CANAL ADDWORD ( pak, 0x0000 ); ADDWORD ( pak, 0x0000 ); ADDWORD ( pak, thisclient->level ); ADDWORD ( pak, thisclient->job ); ADDSTRING ( pak, thisclient->charname ); ADDBYTE ( pak, 0x00 ); otherclient->SendPacket(&pak); } return true; }
// Party Chat bool CWorldServer::pakPartyChat( CPlayer* thisclient, CPacket* P ) { CParty* party = thisclient->Party->party; if(party==NULL) return true; BEGINPACKET( pak, 0x786 ); ADDWORD ( pak, thisclient->clientid ); ADDSTRING ( pak, P->Buffer ); ADDBYTE ( pak, 0 ); party->SendToMembers( &pak ); return true; }
// delete/resurect character bool CCharServer::pakDeleteChar( CCharClient* thisclient, CPacket* P ) { if(!thisclient->isLoggedIn) return false; char* name = (char*)&P->Buffer[2]; MYSQL_RES *result; MYSQL_ROW row; result = DB->QStore("SELECT account_name FROM characters WHERE char_name='%s' LIMIT 1", name); if(result==NULL) return false; row = mysql_fetch_row(result); if (strcmp(row[0], thisclient->username)!=0) { Log(MSG_HACK, "User %s tried deleting another users (%s) character.", thisclient->username, name); DB->QFree( ); return false; } DB->QFree( ); short int action = GETBYTE((*P), 1 ); unsigned long int DeleteTime = 0; switch(action) { case 0x00://Resurect { DeleteTime = 0; if(!DB->QExecute(" UPDATE characters SET deletetime=0 WHERE char_name='%s'", (char*)&P->Buffer[2] )) return false; } break; case 0x01://Delete { DeleteTime = GetServerTime( ) + Config.DeleteTime; if(!DB->QExecute(" UPDATE characters SET deletetime=%i WHERE char_name='%s'", DeleteTime, (char*)&P->Buffer[2] )) return false; } break; } BEGINPACKET( pak, 0x714 ); if(DeleteTime > 0 ) { ADDDWORD ( pak, Config.DeleteTime ); } else { ADDDWORD ( pak, 0x00000000 ); } ADDSTRING ( pak, (char*)&P->Buffer[2] ); ADDBYTE ( pak, 0x00 ); thisclient->SendPacket( &pak ); return true; }
void CPlayer::UpdateInventory( unsigned int slot1, unsigned int slot2 ) { if(slot1==0xffff && slot2==0xffff) return; BEGINPACKET( pak, 0x718 ); if(slot2!=0xffff && slot2!=0xffff) {ADDBYTE( pak, 2 );} else {ADDBYTE( pak, 1 );} if(slot1!=0xffff) { ADDBYTE ( pak, slot1); ADDWORD ( pak, GServer->BuildItemHead( items[slot1] ) ); ADDDWORD ( pak, GServer->BuildItemData( items[slot1] ) ); } if(slot2!=0xffff) { ADDBYTE ( pak, slot2 ); ADDWORD ( pak, GServer->BuildItemHead( items[slot2] ) ); ADDDWORD ( pak, GServer->BuildItemData( items[slot2] ) ); } client->SendPacket( &pak ); }
void CPlayer::UpdateInventory( unsigned int slot1, unsigned int slot2 ) { if(slot1 == 0xffff && slot2 == 0xffff) return; //neither slot is valid BEGINPACKET( pak, 0x718 ); if(slot1 != 0xffff && slot2 != 0xffff) {ADDBYTE( pak, 2 );} //both slots are valid else {ADDBYTE( pak, 1 );} //one of the slots is valid if(slot1 != 0xffff) { ADDBYTE ( pak, slot1); ADDWORD ( pak, GServer->BuildItemHead( items[slot1] ) ); ADDDWORD ( pak, GServer->BuildItemData( items[slot1] ) ); } if(slot2 != 0xffff) { ADDBYTE ( pak, slot2 ); ADDWORD ( pak, GServer->BuildItemHead( items[slot2] ) ); ADDDWORD ( pak, GServer->BuildItemData( items[slot2] ) ); } client->SendPacket( &pak ); }
// Move a mob (specified point) void CMonster::MoveTo( fPoint nPos, bool randcircle ) { if(randcircle) Position->destiny = GServer->RandInCircle( nPos, 5 ); else Position->destiny = nPos; // Position->lastMoveTime = clock(); BEGINPACKET( pak, 0x797 ); ADDWORD ( pak, clientid ); ADDWORD ( pak, 0x0000 ); // ??? ADDWORD ( pak, Stats->Move_Speed ); ADDFLOAT ( pak, Position->destiny.x*100 ); ADDFLOAT ( pak, Position->destiny.y*100 ); ADDWORD ( pak, 0xcdcd ); ADDBYTE ( pak, 0x01 ); GServer->SendToVisible(&pak, this ); }
// Move a mob (random place) void CMonster::Move( ) { Position->destiny = GServer->RandInCircle( Position->source, 10 ); // Position->lastMoveTime = clock(); ClearBattle( Battle ); BEGINPACKET( pak, 0x797 ); ADDWORD ( pak, clientid ); ADDWORD ( pak, 0x0000 );//??? ADDWORD ( pak, Stats->Move_Speed ); //speed ADDFLOAT ( pak, Position->destiny.x*100 ); ADDFLOAT ( pak, Position->destiny.y*100 ); ADDWORD ( pak, 0xcdcd ); ADDBYTE ( pak, 0x01 ); GServer->SendToVisible(&pak, this); }
// ??? bool CCharServer::pak7e5 ( CCharClient* thisclient, CPacket* P ) { if(!thisclient->isLoggedIn) return false; BYTE action = GETBYTE((*P),0); switch(action) { case 0x03: BEGINPACKET( pak, 0x7e5 ); ADDBYTE ( pak, 0x01 ); ADDWORD ( pak, 0x0000 ); thisclient->SendPacket( &pak ); break; default: Log( MSG_WARNING,"Unknown 7e5 action %i",action); } return true; }
// Do player identification bool CCharServer::pakDoIdentify( CCharClient* thisclient, CPacket* P ) { if (thisclient->isLoggedIn) return false; MYSQL_RES *result; MYSQL_ROW row; thisclient->userid = GETDWORD((*P), 0x00); memcpy( thisclient->password, &P->Buffer[4], 32 ); result = DB->QStore("SELECT username,lastsvr,accesslevel,platinum FROM accounts WHERE id=%i AND password='******'", thisclient->userid, thisclient->password); if(result==NULL) return false; if (mysql_num_rows( result ) != 1) { Log( MSG_HACK, "Someone tried to connect to char server with an invalid account" ); DB->QFree( ); return false; } else { row = mysql_fetch_row(result); strncpy(thisclient->username, row[0],16); thisclient->channel = atoi(row[1]); thisclient->accesslevel = atoi(row[2]); thisclient->platinum = atoi(row[3]); DB->QFree( ); } Log( MSG_INFO,"User '%s'(#%i) logged in", thisclient->username, thisclient->userid ); BEGINPACKET( pak, 0x070c ); ADDBYTE ( pak, 0 ); ADDDWORD ( pak, 0x87654321); ADDDWORD ( pak, 0x00000000 ); thisclient->SendPacket( &pak ); result = DB->QStore( "SELECT online FROM accounts WHERE username='******'", thisclient->username ); if(result==NULL) return false; row = mysql_fetch_row(result); bool online = atoi(row[0]); DB->QFree( ); if(online) return false; if(!DB->QExecute( "UPDATE accounts SET online=1 WHERE username='******'", thisclient->username )) return false; thisclient->isLoggedIn = true; return true; }
// Move a mob (random place) void CMonster::Move( ) { Position->destiny = GServer->RandInCircle( Position->source, 10 ); // Position->lastMoveTime = clock(); ClearBattle( Battle ); BEGINPACKET( pak, 0x797 ); ADDWORD ( pak, clientid ); ADDWORD ( pak, 0x0000 );//??? ADDWORD ( pak, Stats->Move_Speed ); //speed ADDFLOAT ( pak, Position->destiny.x*100 ); ADDFLOAT ( pak, Position->destiny.y*100 ); ADDWORD ( pak, 0xcdcd ); //ADDBYTE ( pak, 0x01 ); ADDBYTE ( pak, thisnpc->stance ); //AIP GServer->SendToVisible(&pak, this); if(Position->Map==8) Log(MSG_INFO,"Move (%.2f;%.2f) to (%.2f;%.2f)",Position->current.x,Position->current.y,Position->destiny.x,Position->destiny.y); }
// Move a mob (specified point) void CMonster::MoveTo( fPoint nPos, bool randcircle ) { if(randcircle) Position->destiny = GServer->RandInCircle( nPos, 5 ); else Position->destiny = nPos; // Position->lastMoveTime = clock(); BEGINPACKET( pak, 0x797 ); ADDWORD ( pak, clientid ); ADDWORD ( pak, 0x0000 ); // ??? ADDWORD ( pak, Stats->Move_Speed ); ADDFLOAT ( pak, Position->destiny.x*100 ); ADDFLOAT ( pak, Position->destiny.y*100 ); ADDWORD ( pak, 0xcdcd ); //ADDBYTE ( pak, 0x01 ); ADDBYTE ( pak, thisnpc->stance); //AIP GServer->SendToVisible(&pak, this ); if(Position->Map==8) Log(MSG_INFO,"MoveTo (%.2f;%.2f) to (%.2f;%.2f)",Position->current.x,Position->current.y,Position->destiny.x,Position->destiny.y); }
// upload clanmark bool CCharServer::pakUploadCM ( CCharClient* thisclient, CPacket* P ) { CClans* thisclan = GetClanByID( thisclient->clanid ); if(thisclan==NULL) return true; FILE* fh = fopen( "clanmark/clanmark.cnt", "r+b" ); if(fh==NULL) { Log( MSG_WARNING, "Error opening clanmark counter file" ); return true; } rewind(fh); unsigned int cmid = 0;// this will be our clanmark id unsigned int tcmid = 0;// this will be to update the id fread( &cmid, 1, 4, fh ); tcmid = cmid+1; rewind(fh); fwrite( &tcmid, 1, 4, fh ); fclose(fh); thisclan->logo = GETWORD((*P), 0 );//cmid; thisclan->back = 0; char filename[30]; sprintf( filename, "clanmark/%u.cm", thisclan->logo ); fh = fopen( filename, "w+b" ); if(fh==NULL) { Log( MSG_WARNING, "Error creating clanmark file" ); return true; } rewind(fh); for(unsigned int i=0;i<P->Size-6;i++) fwrite( &P->Buffer[i], 1, 1, fh ); fclose(fh); DB->QExecute("UPDATE list_clan SET logo=%i,back=0 WHERE id=%i",thisclan->logo, thisclient->clanid ); BEGINPACKET( pak, 0x7e1 );//update clan info in world ADDBYTE ( pak, 0xff ); ADDWORD ( pak, thisclient->clanid ); ADDDWORD ( pak, cmid ); for(int i=0;i<ChannelList.size();i++) send( ChannelList.at(i)->sock, (char*)&pak, pak.Size, 0 ); return true; }
// NPC Announce to the server bool CWorldServer::NPCAnnounce( char* msg, char* npc, int mapid) { //LMA: We only announce in the NPC's map //Log(MSG_INFO,"%s announces: %s",npc,msg); BEGINPACKET( pak, 0x702 ); ADDSTRING( pak, npc ); ADDSTRING( pak, "> " ); ADDSTRING( pak, msg); ADDBYTE( pak, 0x00); if(mapid!=0) { SendToMap ( &pak, mapid ); } else { SendToAll( &pak ); } return true; }
// use buff skill void CCharacter::UseBuffSkill( CCharacter* Target, CSkills* skill ) { bool bflag = false; bflag = GServer->AddBuffs( skill, Target, GetInt( ) ); if(skill->nbuffs > 0 && bflag == true) { BEGINPACKET( pak, 0x7b5 ); ADDWORD ( pak, Target->clientid ); ADDWORD ( pak, clientid ); ADDWORD ( pak, Battle->skillid ); ADDWORD ( pak, GetInt( )/4 ); ADDBYTE ( pak, skill->nbuffs ); GServer->SendToVisible( &pak, Target ); //Log(MSG_DEBUG,"Buff Skill. buffs applied 1"); } BEGINPACKET( pak, 0x7b9); ADDWORD ( pak, clientid); ADDWORD ( pak, Battle->skillid); ADDWORD ( pak, 1); GServer->SendToVisible( &pak, this ); //Log(MSG_DEBUG,"Buff Skill. buffs applied 2 nbuffs = %i",skill->nbuffs); //Log(MSG_INFO,"Target HP = %i",Target->Stats->HP); }
// Send clan members bool CCharServer::pakClanMembers ( CCharClient* thisclient ) { CClans* thisclan = (CClans*) GetClanByID( thisclient->clanid ); if(thisclan!=NULL) { BEGINPACKET( pak, 0x7e0 ); ADDBYTE ( pak, 0x72 );//Send clan members for(UINT i=0;i<thisclan->ClanMembers.size( );i++) { CClanMembers* thismember = thisclan->ClanMembers.at( i ); if(thismember==NULL) continue; CCharClient* otherclient = (CCharClient*) GetClientByID (thismember->id); if(otherclient!=NULL) { ADDBYTE ( pak, thismember->clan_rank ); //clan rank ADDBYTE ( pak, otherclient->channel ); //channel (0x01 = channel 1) ADDWORD ( pak, 0x0000 ); ADDWORD ( pak, 0x0000 ); ADDWORD ( pak, otherclient->level );// Level ADDWORD ( pak, otherclient->job );// Job } else { ADDBYTE ( pak, thismember->clan_rank); //clan rank ADDBYTE ( pak, 0xff ); //channel (0xff = offline) ADDWORD ( pak, 0x0000 ); ADDWORD ( pak, 0x0000 ); ADDWORD ( pak, 0x0000 );// Level ADDWORD ( pak, 0x0000 );// Job } ADDSTRING ( pak, thismember->name ); ADDBYTE ( pak, 0x00 ); } thisclient->SendPacketCpy( &pak ); } return true; }
// Messenger actions (add/remove/invite) bool CCharServer::pakMessengerManager ( CCharClient* thisclient, CPacket* P ) { BYTE action = GETBYTE((*P),0); switch (action) { case 0x01://wanna be my friend? { char* nick = new (nothrow) char[P->Size-7]; if(nick==NULL) { Log(MSG_ERROR, "Error allocing memory: pakMessengerManager 1" ); return false; } memcpy( nick, &P->Buffer[1], P->Size-7 ); Log(MSG_INFO,"%s Trying to invite %s",nick,thisclient->charname); CCharClient* otherclient = (CCharClient*) GetClientByName (nick); if(otherclient!=NULL) {//Send friend invitation (check this one) BEGINPACKET( pak, 0x7e1 ); ADDBYTE ( pak, 0x01 ); ADDWORD ( pak, 0x0000 ); ADDSTRING ( pak, thisclient->charname ); ADDBYTE ( pak, 0x00 ); otherclient->SendPacket(&pak); Log(MSG_INFO,"%s exists, invite sent to %s",nick,otherclient->charname); } else {//This charname doesnt exist BEGINPACKET( pak, 0x7e1 ); ADDBYTE ( pak, 0x04 ); ADDSTRING ( pak, nick ); ADDBYTE ( pak, 0x00 ); thisclient->SendPacket(&pak); Log(MSG_INFO,"invite: %s doesn't exist",nick); } delete []nick; } break; case 0x02://yes i want { char* nick = new (nothrow) char[P->Size-9]; if(nick==NULL) { Log(MSG_ERROR, "Error allocing memory: pakMessengerManager 2" ); return false; } memcpy( nick, &P->Buffer[3], P->Size-9 ); CCharClient* otherclient = (CCharClient*) GetClientByName (nick); if(otherclient!=NULL) { BEGINPACKET( pak, 0x7e1 ); ADDBYTE ( pak, 0x02 ); ADDWORD ( pak, thisclient->charid ); ADDBYTE ( pak, 0x00 ); ADDWORD ( pak, 0x0000 ); ADDSTRING ( pak, thisclient->charname ); ADDBYTE ( pak, 0x00); otherclient->SendPacket(&pak); //Add friend to my friend list(sql) if(!DB->QExecute("INSERT INTO list_friend (id,idfriend,namefriend) VALUES (%i,%i,'%s')",otherclient->charid,thisclient->charid,thisclient->charname)) { Log(MSG_WARNING,"error addind %s to %s friend list",otherclient->charname,thisclient->charname); return false; } CFriendList * newfriend1 = new (nothrow) CFriendList; if(newfriend1==NULL) return false; newfriend1->id = otherclient->charid; //friendid strcpy(newfriend1->name, otherclient->charname); //friend name thisclient->FriendList.push_back( newfriend1 ); RESETPACKET( pak, 0x7e1 ); ADDBYTE ( pak, 0x02 ); ADDWORD ( pak, otherclient->charid ); ADDBYTE ( pak, 0x00 ); ADDWORD ( pak, 0x0000 ); ADDSTRING ( pak, otherclient->charname ); ADDBYTE ( pak, 0x00); thisclient->SendPacket(&pak); //Add me to his friend list (sql) if(!DB->QExecute("INSERT INTO list_friend (id,idfriend,namefriend) VALUES (%i,%i,'%s')", thisclient->charid,otherclient->charid,otherclient->charname)) return false; CFriendList * newfriend2 = new (nothrow) CFriendList; if(newfriend2==NULL) return false; newfriend2->id = thisclient->charid; //friendid strcpy(newfriend2->name, thisclient->charname); //friend name otherclient->FriendList.push_back( newfriend2 ); Log(MSG_INFO,"accept %s ok",nick); } else//not founded { BEGINPACKET( pak, 0x7e1 ); ADDBYTE ( pak, 0x04 ); ADDSTRING ( pak, nick ); ADDBYTE ( pak, 0x00 ); thisclient->SendPacket(&pak); Log(MSG_INFO,"accept: %s doesn't exist",nick); } delete []nick; } break; case 0x03://no, i dont want { char* nick = new (nothrow) char[P->Size-9]; if(nick==NULL) { Log(MSG_ERROR, "Error allocing memory: pakMessengerManager 3" ); return false; } memcpy( nick, &P->Buffer[3], P->Size-9 ); CCharClient* otherclient = (CCharClient*) GetClientByName (nick); if(otherclient!=NULL) { BEGINPACKET( pak, 0x7e1 ); ADDBYTE ( pak, 0x03 ); ADDSTRING ( pak, thisclient->charname ); ADDBYTE ( pak, 0x00); otherclient->SendPacket(&pak); Log(MSG_INFO,"refuse: %s ok",nick); } else { BEGINPACKET( pak, 0x7e1 ); ADDBYTE ( pak, 0x04 ); ADDWORD ( pak, 0x0000 ); ADDSTRING ( pak, nick ); ADDBYTE ( pak, 0x00 ); thisclient->SendPacket(&pak); Log(MSG_INFO,"refuse: %s doesn't exist",nick); } } break; case 0x05://delete user. { WORD id = GETWORD ((*P),1); if(!DB->QExecute("DELETE FROM list_friend WHERE id=%i and idfriend=%i",thisclient->charid,id)) { Log(MSG_INFO,"user failed to delete friend slot %i",thisclient->charname,id); return false; } Log(MSG_INFO,"user %s deletes friend slot %i",thisclient->charname,id); CCharClient* otherclient = (CCharClient*) GetClientByID(id); if(otherclient!=NULL) { ChangeMessengerStatus ( thisclient, otherclient, 0x08); } } break; case 0xfa://messenger logout { WORD id = GETWORD ((*P),1); CCharClient* ctherclient = (CCharClient*) GetClientByID(id); if(ctherclient==NULL) return true; ctherclient->logout = true; for(UINT i=0;i<ctherclient->FriendList.size();i++) { CFriendList* thisfriend = ctherclient->FriendList.at( i ); CCharClient* otherclient = (CCharClient*) GetClientByID( thisfriend->id ); if(otherclient!=NULL) { ChangeMessengerStatus ( ctherclient, otherclient, 0x08); } } //Logout in clan ClanLogout ( ctherclient ); } break; default: Log( MSG_INFO, "Friend action unknown: %i", action ); break; } return true; }
// Spawn a monster //LMA: added handling of skill summons. void CMonster::SpawnMonster( CPlayer* player, CMonster* thismon ) { BEGINPACKET( pak, 0x792 ); ADDWORD ( pak, clientid ); ADDFLOAT ( pak, Position->current.x*100 ); ADDFLOAT ( pak, Position->current.y*100 ); if((thismon->bonushp>0||thismon->bonusmp>0)&&(thismon->skillid>0)) { ADDFLOAT ( pak, 0xcdcdcdcd ); ADDFLOAT ( pak, 0xcdcdcdcd ); } else { ADDFLOAT ( pak, Position->destiny.x*100 ); ADDFLOAT ( pak, Position->destiny.y*100 ); } if(IsDead( )) { ADDWORD ( pak, 0x0003 ); ADDWORD ( pak, 0x0000 ); } else if(IsOnBattle( )) { //LMA: for supportive summons (lucky ghost...) if(Battle->bufftarget==Battle->target) { ADDWORD ( pak, 0x0002 ); ADDWORD ( pak, 0x0000 ); } else { ADDWORD ( pak, 0x0002 ); ADDWORD ( pak, Battle->target ); } } else if(IsMoving( )) { ADDWORD ( pak, 0x0001 ); ADDWORD ( pak, 0x0000 ); } else { ADDWORD ( pak, 0x0000 ); ADDWORD ( pak, 0x0000 ); } if(IsSummon( ) ) { ADDBYTE ( pak, 0x01 ); } else { ADDBYTE ( pak, 0x00 ); } //LMA: Little check, for now we "only" have a DWORD for monster's HP so there is a limit //broken by some monsters (Turak boss) if(Stats->HP>MAXHPMOB) { LogDebugPriority(3); LogDebug("Too much HP for monster %i (%I64i->%I64i)",thismon->montype,Stats->HP,MAXHPMOB); LogDebugPriority(4); Stats->HP=(long long) MAXHPMOB; } ADDDWORD ( pak, Stats->HP ); if(thismon->owner != player->clientid) { CMap* map = GServer->MapList.Index[Position->Map]; //LMA: adding team... if (thismon->team!=0) { ADDDWORD( pak,thismon->team); } else { if(IsSummon( ) && map->allowpvp!=0) { //Hostil ADDDWORD( pak, 0x00000064 ); } else if (IsSummon( ) && map->allowpvp==0) { //Friendly ADDDWORD ( pak, 0x00000000 ); } else { //Hostil ADDDWORD( pak, 0x00000064 ); } //TODO: LMA, test if necessary or not anymore... /* else if(thismon->montype>=1474&&thismon->montype<=1489) { //LMA: Xmas trees are friendly. ADDDWORD ( pak, 0x00000000 ); } */ } } else { //Friendly ADDDWORD( pak, 0x00000000 ); } ADDDWORD( pak, GServer->BuildBuffs( this ) ); ADDWORD ( pak, montype ); ADDWORD ( pak, 0x0000 ); if(IsSummon( )) { ADDWORD( pak, owner ); if (thismon->skillid>0) { ADDWORD( pak, thismon->skillid ); //id del skill (si es summon de skill) } else { ADDWORD( pak, 0x0000 ); //id del skill (si es summon de skill) } } player->client->SendPacket( &pak ); //LMA: supportive summons (lucky ghost) if(IsSummon()&&buffid>0&&(player==GetOwner())) { Log(MSG_INFO,"The summon is spawned"); /*CPlayer* player = GetOwner( ); if (ownplayer==NULL) return true;*/ StartAction( player,SUMMON_BUFF,buffid); Log(MSG_INFO,"completly"); buffid=0; //only one buff } }
// Party Manager bool CWorldServer::pakPartyManager( CPlayer* thisclient, CPacket* P ) { unsigned int action = GETBYTE((*P),0); switch(action) { case 0x02://Acepto { if(thisclient->Party->party!=NULL)// have party return true; unsigned int clientid = GETWORD((*P),1); if (clientid == thisclient->clientid) { Log(MSG_WARNING, "User %s tried to party with himself\n", thisclient->CharInfo->charname); return false; //kick the cheater } CPlayer* otherclient = GetClientByID( clientid, thisclient->Position->Map ); if(otherclient==NULL) { BEGINPACKET( pak, 0x7d1 ); ADDBYTE ( pak, 0x00 );//No encontro el ID ADDWORD ( pak, clientid ); ADDBYTE ( pak, 0x00 ); thisclient->client->SendPacket( &pak ); return true; } CParty* party = otherclient->Party->party; if(party!=NULL) { //LMA: Refreshing Capacity if needed party->RefreshMax(); if(party->Members.size()>=party->Capacity) { SendSysMsg( thisclient, "Party is Full" ); return true; } } if(abs(otherclient->Stats->Level-thisclient->Stats->Level)>(Config.Partygap+1)) { BEGINPACKET( pak, 0x7d1 ); ADDBYTE ( pak, 0x07 );//Level inapropiado ADDWORD ( pak, clientid ); ADDBYTE ( pak, 0x00 ); thisclient->client->SendPacket( &pak ); return true; } BEGINPACKET( pak, 0x7d1 ); ADDBYTE ( pak, 0x02 );//Acepto Party ADDWORD ( pak, otherclient->clientid ); ADDBYTE ( pak, 0x00 ); otherclient->client->SendPacket( &pak ); if( party==NULL ) { // new party CParty* thisparty = new CParty; thisparty->AddPlayer( otherclient ); AddParty( thisparty ); otherclient->Party->IsMaster = true; party = thisparty; } //Send Party Level and Party Exp RESETPACKET( pak, 0x7d4 ); // ADDBYTE ( pak, party->PartyLevel ); ADDDWORD ( pak, party->Exp ); thisclient->client->SendPacket( &pak ); thisclient->Party->IsMaster = false; // Send New Party Member info to other players RESETPACKET( pak, 0x7d2 ); ADDBYTE ( pak, party->Option ); ADDBYTE ( pak, 0x01 ); ADDDWORD ( pak, thisclient->CharInfo->charid ); ADDWORD ( pak, thisclient->clientid ); ADDWORD ( pak, thisclient->Stats->MaxHP ); ADDWORD ( pak, thisclient->Stats->HP ); //ADDDWORD ( pak, 0x00000000 );//Tomiz: Was not commented before ADDDWORD ( pak, BuildBuffs( thisclient ));//Tomiz: Buff Data //ADDDWORD ( pak, 0x0000000f );//Tomiz: Was not commented before ADDDWORD ( pak, 0x1f40008c );//Tomiz ADDWORD ( pak, 0x1388 ); ADDSTRING ( pak, thisclient->CharInfo->charname ); ADDBYTE ( pak, 0x00 ); party->SendToMembers( &pak ); // Send To New Party Member the members List RESETPACKET( pak, 0x7d2 ); ADDBYTE ( pak, party->Option ); ADDBYTE ( pak, party->Members.size() ); for(int i=0;i<party->Members.size();i++) { CPlayer* member= party->Members.at(i); ADDDWORD ( pak, member->CharInfo->charid ); ADDWORD ( pak, member->clientid ); ADDWORD ( pak, member->Stats->MaxHP ); ADDWORD ( pak, member->Stats->HP ); //ADDDWORD ( pak, 0x00000000 );//Tomiz: Was not commented before ADDDWORD ( pak, BuildBuffs( member ));//Tomiz: Buff Data //ADDDWORD ( pak, 0x0000000f );//Tomiz: Was not commented before ADDDWORD ( pak, 0x7200005b );//Tomiz ADDWORD ( pak, 0x1388 ); ADDSTRING ( pak, member->CharInfo->charname ); ADDBYTE ( pak, 0x00 ); } thisclient->client->SendPacket( &pak ); party->AddPlayer( thisclient ); } break; case 0x04://No acepto { unsigned int clientid = GETWORD((*P),1); CPlayer* otherclient = GetClientByID( clientid, thisclient->Position->Map ); if(otherclient==NULL) { BEGINPACKET( pak, 0x7d1 ); ADDBYTE ( pak, 0x00 );//No encontro el ID ADDWORD ( pak, clientid ); ADDBYTE ( pak, 0x00 ); thisclient->client->SendPacket( &pak ); return true; } BEGINPACKET( pak, 0x7d1 ); ADDBYTE ( pak, 0x04 );//No acepto ADDWORD ( pak, thisclient->clientid ); ADDBYTE ( pak, 0x00 ); otherclient->client->SendPacket( &pak ); } break; default: Log(MSG_WARNING,"Party Manager unknown action: %i", action); } return true; }
// This cleans up our clients mess :P void CWorldServer::OnClientDisconnect( CClientSocket* thisclient ) { if(thisclient->player==NULL) return; CPlayer* player = (CPlayer*)thisclient->player; if(!player->Session->isLoggedIn) return; if(!player->Saved) { player->savedata(); player->Session->isLoggedIn = false; //send packet to change messenger status (offline) BEGINPACKET( pak, 0x7e1 ); ADDBYTE ( pak, 0xfa ); ADDWORD ( pak, player->CharInfo->charid ); ADDBYTE ( pak, 0x00 ); cryptPacket( (char*)&pak, NULL ); send( csock, (char*)&pak, pak.Size, 0 ); } if ( player->Fairy ) { FairyList.at(player->FairyListIndex)->assigned = false; FairyList.at(player->FairyListIndex)->LastTime = clock(); FairyList.at(player->FairyListIndex)->ListIndex = 0; FairyList.at(player->FairyListIndex)->WaitTime = Config.FairyWait * (rand()% GetFairyRange(1)+ GetFairyRange(0)); player->Fairy = false; player->FairyListIndex = 0; DoFairyStuff(player, 0); // recalculate FairyMax Config.FairyMax = (int)ceil((float)ClientList.size() / 50.0); //(1 fairy more every 50 player) } if(player->Party->party!=NULL) { CParty* party = player->Party->party; BEGINPACKET( pak, 0x7d2 ); ADDWORD ( pak, 0xff00 ); ADDDWORD ( pak, player->CharInfo->charid ); bool pflag = false; party->RemovePlayer( player ); if(party->Members.size()>1) { for(UINT i=0;i<party->Members.size();i++) { CPlayer* othermember = party->Members.at(i); if(!pflag) { ADDDWORD( pak, othermember->CharInfo->charid ); if(player->Party->IsMaster) othermember->Party->IsMaster = true; pflag = true; } othermember->client->SendPacket( &pak ); } } else { for(UINT i=0;i<party->Members.size();i++) { CPlayer* othermember = party->Members.at(i); BEGINPACKET( pak, 0x7d1 ); ADDBYTE ( pak, 0x05 ); ADDWORD ( pak, 0x0000 ); ADDWORD ( pak, 0x0000 ); othermember->client->SendPacket( &pak ); othermember->Party->party = NULL; othermember->Party->IsMaster = true; } RemoveParty( party ); delete party; party = NULL; } } DB->QExecute("UPDATE accounts SET online=false where id=%u", player->Session->userid ); }
// Spawn a monster //LMA: added handling of skill summons. void CMonster::SpawnMonster( CPlayer* player, CMonster* thismon ) { BEGINPACKET( pak, 0x792 ); //struct tag_ADD_CHAR ADDWORD ( pak, clientid ); ADDFLOAT ( pak, Position->current.x*100 ); ADDFLOAT ( pak, Position->current.y*100 ); //current X and Y position if((thismon->bonushp > 0 || thismon->bonusmp > 0) && (thismon->skillid > 0)) //What is this? It seems to be sending this in place of destination if the monster is not able to move. Bonfires and so on { ADDFLOAT ( pak, 0xcdcdcdcd ); ADDFLOAT ( pak, 0xcdcdcdcd ); } else { ADDFLOAT ( pak, Position->destiny.x*100 ); //Destination position. This should always follow current position ADDFLOAT ( pak, Position->destiny.y*100 ); } // next 2 WORDS are m_wCommand and m_wTargetOBJ if(IsDead( )) { ADDWORD ( pak, 0x0003 ); ADDWORD ( pak, 0x0000 ); } else if(IsOnBattle( )) { //LMA: for supportive summons (lucky ghost...) if(Battle->bufftarget == Battle->target) { ADDWORD ( pak, 0x0002 ); ADDWORD ( pak, 0x0000 ); } else { ADDWORD ( pak, 0x0002 ); ADDWORD ( pak, Battle->target ); } } else if(IsMoving( )) { ADDWORD ( pak, 0x0001 ); ADDWORD ( pak, 0x0000 ); } else { ADDWORD ( pak, 0x0000 ); ADDWORD ( pak, 0x0000 ); } ADDBYTE ( pak, thisnpc->stance ); ADDWORD ( pak, Stats->HP ); ADDWORD ( pak, 0x0000 ); // now Team Number if(thismon->owner != player->clientid) { CMap* map = GServer->MapList.Index[Position->Map]; //LMA: adding team... if (thismon->team!=0) { ADDWORD( pak,thismon->team); } else { if(IsSummon( ) && map->allowpvp!=0) { //Hostil ADDWORD( pak, 0x0064 ); } else if (IsSummon( ) && map->allowpvp==0) { //Friendly ADDWORD ( pak, 0x0000 ); } else { //Hostil ADDWORD( pak, 0x0064 ); } } } else { //Friendly ADDWORD( pak, 0x0000 ); } ADDWORD ( pak, 0x0000 ); //and finally DWORD StatusFlag ADDDWORD( pak, GServer->BuildBuffs( this ) ); //struct gsv_MOB_CHAR ADDWORD ( pak, montype ); ADDWORD ( pak, 0x0000 ); ADDWORD ( pak, thismon->Stats->Level ); ADDWORD ( pak, thismon->Stats->Size ); player->client->SendPacket( &pak ); //LMA: supportive summons (lucky ghost) //PY: Another special case. Will probably need to remove this later if(IsSummon() && buffid > 0 && (player == GetOwner())) { Log(MSG_INFO,"The summon is spawned"); /*CPlayer* player = GetOwner( ); if (ownplayer==NULL) return true;*/ StartAction( player,SUMMON_BUFF,buffid); Log(MSG_INFO,"completly"); buffid=0; //only one buff } }
// Spawn a monster //LMA: added handling of skill summons. void CMonster::SpawnMonster( CPlayer* player, CMonster* thismon ) { BEGINPACKET( pak, 0x792 ); ADDWORD ( pak, clientid ); ADDFLOAT ( pak, Position->current.x*100 ); ADDFLOAT ( pak, Position->current.y*100 ); ADDDWORD ( pak, 0xcdcdcdcd ); ADDDWORD ( pak, 0xcdcdcdcd ); if(IsDead( )) { ADDWORD ( pak, 0x0003 ); ADDWORD ( pak, 0x0000 ); } else if(IsOnBattle( )) { //LMA: for supportive summons (lucky ghost...) if(Battle->bufftarget==Battle->target) { ADDWORD ( pak, 0x0002 ); ADDWORD ( pak, 0x0000 ); } else { ADDWORD ( pak, 0x0002 ); ADDWORD ( pak, Battle->target ); } } else if(IsMoving( )) { ADDWORD ( pak, 0x0001 ); ADDWORD ( pak, 0x0000 ); } else { ADDWORD ( pak, 0x0000 ); ADDWORD ( pak, 0x0000 ); } if(IsSummon( ) ) { ADDBYTE ( pak, 0x01 ); } else { ADDBYTE ( pak, 0x00 ); } ADDWORD(pak, 0x0000); if(Stats->HP > MAXHPMOB) { Stats->HP = (long long) MAXHPMOB; } ADDDWORD ( pak, Stats->HP ); if(thismon->owner != player->clientid) { CMap* map = GServer->MapList.Index[Position->Map]; if (thismon->team!=0) { ADDDWORD( pak,thismon->team); } else { if(IsSummon( ) && map->allowpvp!=0) { //Hostil ADDDWORD( pak, 0x00000064 ); } else if (IsSummon( ) && map->allowpvp==0) { //Friendly ADDDWORD ( pak, 2 ); } else { //Hostil ADDDWORD( pak, 0x00000064 ); } } } else { //Friendly ADDDWORD( pak, 2 ); } ADDDWORD( pak, GServer->BuildBuffs( this ) ); ADDDWORD ( pak, montype ); if(IsSummon( )) { ADDWORD( pak, owner ); if (thismon->skillid>0) { ADDWORD( pak, thismon->skillid ); //id del skill (si es summon de skill) } else { ADDWORD( pak, 0x0000 ); //id del skill (si es summon de skill) } } player->client->SendPacket( &pak ); //LMA: supportive summons (lucky ghost) if(IsSummon()&&buffid>0&&(player==GetOwner())) { Log(MSG_INFO,"The summon is spawned"); StartAction( player,SUMMON_BUFF,buffid); Log(MSG_INFO,"completly"); buffid=0; //only one buff } }
// Party Actions [invite/leave/kick] bool CWorldServer::pakPartyActions( CPlayer* thisclient, CPacket* P ) { unsigned int action = GETBYTE((*P),0); CMap* map = MapList.Index[thisclient->Position->Map]; switch(action) { case 0x00://Invita a new party case 0x01://invite a existent party { UINT clientid = GETWORD((*P),1); if(thisclient->Party->party!=NULL) { //LMA: Refreshing Capacity if needed thisclient->Party->party->RefreshMax(); if(thisclient->Party->party->Members.size()>=thisclient->Party->party->Capacity) { SendSysMsg( thisclient, "Party is full" ); return true; } } CPlayer* otherclient = map->GetPlayerInMap( clientid );// have to be in same map if(otherclient==NULL) { BEGINPACKET( pak, 0x7d1 ); ADDBYTE ( pak, 0x00 );//No encontro el ID ADDWORD ( pak, clientid ); ADDBYTE ( pak, 0x00 ); thisclient->client->SendPacket( &pak ); return true; } if(otherclient->Party->party!=NULL) { BEGINPACKET( pak, 0x7d1 ); ADDBYTE ( pak, 0x01 );//No puede ser solicitado (ya tiene party) ADDWORD ( pak, clientid ); ADDBYTE ( pak, 0x00 ); thisclient->client->SendPacket( &pak ); return true; } if(abs(otherclient->Stats->Level-thisclient->Stats->Level)>(Config.Partygap+1)) { BEGINPACKET( pak, 0x7d1 ); ADDBYTE ( pak, 0x07 );//Level inapropiado ADDWORD ( pak, clientid ); ADDBYTE ( pak, 0x00 ); thisclient->client->SendPacket( &pak ); return true; } BEGINPACKET( pak, 0x7d0 ); ADDBYTE ( pak, action ); ADDWORD ( pak, thisclient->clientid ); ADDBYTE ( pak, 0x00 ); otherclient->client->SendPacket( &pak ); } break; case 0x02://Leave Party { if(thisclient->Party->party==NULL) return true; CParty* party = thisclient->Party->party; BEGINPACKET( pak, 0x7d2 ); ADDWORD ( pak, 0xff00 ); ADDDWORD ( pak, thisclient->CharInfo->charid ); bool pflag = false; if(!party->RemovePlayer( thisclient )) //if this player is not in this party return true; if(party->Members.size()>1) { for(UINT i=0;i<party->Members.size();i++) { CPlayer* thismember = party->Members.at(i); if(!pflag) { ADDDWORD( pak, thismember->CharInfo->charid ); if(thisclient->Party->IsMaster) thismember->Party->IsMaster = true; pflag = true; } thismember->client->SendPacket( &pak ); } } else { for(UINT i=0;i<party->Members.size();i++) { CPlayer* thismember = party->Members.at(i); BEGINPACKET( pak, 0x7d1 ); ADDBYTE ( pak, 0x05 ); ADDWORD ( pak, 0x0000 ); ADDWORD ( pak, 0x0000 ); thismember->client->SendPacket( &pak ); thismember->Party->party = NULL; thismember->Party->IsMaster = true; } RemoveParty( party ); delete party; } } break; case 0x03: //Tomiz: Give leader New Way { if(thisclient->Party->party==NULL) return true; unsigned int clientid = GETWORD((*P),1); if ( !thisclient->Party->IsMaster || clientid == thisclient->clientid ) return true; CPlayer* otherclient = GetClientByID( clientid ); if(otherclient==NULL) return true; BEGINPACKET(pak, 0x7d1); ADDBYTE(pak, 8); ADDWORD(pak, otherclient->clientid); ADDWORD(pak, 0); otherclient->Party->IsMaster = true; thisclient->Party->IsMaster = false; thisclient->Party->party->SendToMembers( &pak ); } break; case 0x81: //Kick from party { unsigned int charid = GETDWORD((*P),1); if(thisclient->Party->party==NULL) return true; if(!thisclient->Party->IsMaster || thisclient->CharInfo->charid==charid) return true; CParty* party = thisclient->Party->party; CPlayer* thismember = party->GetMemberByCharID( charid ); if(thismember==NULL) return true; BEGINPACKET( pak, 0x7d1 ); // Kicked from party message ADDBYTE ( pak, 0x80 ); ADDDWORD ( pak, thismember->CharInfo->charid ); thismember->client->SendPacket( &pak ); RESETPACKET( pak, 0x7d2 ); ADDWORD ( pak, 0xff00 ); ADDDWORD ( pak, thismember->CharInfo->charid ); bool pflag = false; if(!party->RemovePlayer( thismember )) //if this player is not in this party return true; if(party->Members.size()>1) { for(UINT i=0;i<party->Members.size();i++) { CPlayer* othermember = party->Members.at(i); if(!pflag) { ADDDWORD( pak, othermember->CharInfo->charid ); if(thismember->Party->IsMaster) othermember->Party->IsMaster = true; pflag = true; } othermember->client->SendPacket( &pak ); } } else { for(UINT i=0;i<party->Members.size();i++) { CPlayer* othermember = party->Members.at(i); BEGINPACKET( pak, 0x7d1 ); ADDBYTE ( pak, 0x05 ); ADDWORD ( pak, 0x0000 ); ADDWORD ( pak, 0x0000 ); othermember->client->SendPacket( &pak ); othermember->Party->party = NULL; othermember->Party->IsMaster = true; } RemoveParty( party ); delete party; } } break; default: Log(MSG_WARNING,"Party unknown action: %i", action); } return true; }