1 /////////////////////////////////////////
2 //
3 // OpenLieroX
4 //
5 // code under LGPL, based on JasonBs work,
6 // enhanced by Dark Charlie and Albert Zeyer
7 //
8 //
9 /////////////////////////////////////////
10
11
12 // Client class - Sending routines
13 // Created 22/7/02
14 // Jason Boettcher
15
16 #include "LieroX.h"
17
18 #include "CClientNetEngine.h"
19 #include "StringUtils.h"
20 #include "CClient.h"
21 #include "Protocol.h"
22 #include "CWorm.h"
23 #include "MathLib.h"
24 #include "ChatCommand.h"
25 #include "Command.h"
26 #include "EndianSwap.h"
27 #include "CServer.h"
28 #include "AuxLib.h"
29 #include "CChannel.h"
30 #include "DeprecatedGUI/Menu.h"
31 #include "IRC.h"
32 #include "console.h"
33
34
35
36 // declare them only locally here as nobody really should use them explicitly
37 std::string OldLxCompatibleString(const std::string &Utf8String);
38
39
40 ///////////////////
41 // Send the worm details
SendWormDetails()42 void CClientNetEngine::SendWormDetails()
43 {
44 // Don't flood packets so often
45 // we are checking in w->checkPacketNeeded() if we need to send an update
46 // we are checking with bandwidth if we should add an update
47 /*if ((tLX->currentTime - fLastUpdateSent) <= tLXOptions->fUpdatePeriod)
48 if (tGameInfo.iGameType != GME_LOCAL)
49 return; */
50
51 CBytestream bs;
52 uint i;
53
54 // If all my worms are dead, don't send
55 bool Alive = false;
56 for(i=0;i<client->iNumWorms;i++) {
57 if(client->cLocalWorms[i] && client->cLocalWorms[i]->getAlive()) {
58 Alive = true;
59 break;
60 }
61 }
62
63 if(!Alive)
64 return;
65
66
67 // Check if we need to write the state update
68 bool update = false;
69 for(i = 0; i < client->iNumWorms; i++)
70 if (client->cLocalWorms[i] && client->cLocalWorms[i]->checkPacketNeeded()) {
71 update = true;
72 break;
73 }
74
75 // No update, just quit
76 if (!update)
77 return;
78
79 // TODO: Have we always used the limitcheck from GameServer here?
80 // We should perhaps move it out from GameServer. Looks a bit strange here to use something from GameServer.
81 if( tLX->iGameType == GME_JOIN // we are a client in a netgame
82 && !GameServer::checkUploadBandwidth(client->getChannel()->getOutgoingRate()) )
83 return;
84
85 client->fLastUpdateSent = tLX->currentTime;
86
87 // Write the update
88 bs.writeByte(C2S_UPDATE);
89
90 for(i = 0; i < client->iNumWorms; i++)
91 if(client->cLocalWorms[i])
92 client->cLocalWorms[i]->writePacket(&bs, false, NULL);
93
94 client->bsUnreliable.Append(&bs);
95 }
96
97
SendGameReady()98 void CClientNetEngine::SendGameReady()
99 {
100 CBytestream bs;
101 bs.writeByte(C2S_IMREADY);
102
103 if(client->serverChoosesWeapons()) {
104 bs.writeByte(0);
105 } else {
106 std::list<CWorm*> worms;
107 for(unsigned int i=0;i<client->iNumWorms;i++)
108 if(client->cLocalWorms[i]) worms.push_back(client->cLocalWorms[i]);
109
110 // Send my worm's weapon details
111 bs.writeByte(worms.size());
112 for(std::list<CWorm*>::iterator i = worms.begin(); i != worms.end(); ++i)
113 (*i)->writeWeapons( &bs );
114 }
115
116 client->cNetChan->AddReliablePacketToSend(bs);
117 }
118
119
120 ///////////////////
121 // Send a death
SendDeath(int victim,int killer)122 void CClientNetEngine::SendDeath(int victim, int killer)
123 {
124 CBytestream bs;
125
126 bs.writeByte(C2S_DEATH);
127 bs.writeInt(victim,1);
128 bs.writeInt(killer,1);
129
130 client->cNetChan->AddReliablePacketToSend(bs);
131 }
132
SendDeath(int victim,int killer)133 void CClientNetEngineBeta9::SendDeath(int victim, int killer)
134 {
135 // If we have some damage reports in buffer send them first so clients won't sum up updated damage score and reported damage packet sent later
136 SendReportDamage(true);
137
138 CClientNetEngine::SendDeath(victim, killer);
139 }
140
141
142 ///////////////////
143 // Send a string of text
SendText(const std::string & sText,std::string sWormName)144 void CClientNetEngine::SendText(const std::string& sText, std::string sWormName)
145 {
146 if(HandleDebugCommand(sText)) return;
147
148 bool chat_command = sText.size() >= 2 && sText[0] == '/' && sText[1] != '/';
149
150 if (sText.find("/irc ") == 0 || sText.find("/chat ") == 0) { // Send text to IRC
151 bool res = false;
152 if (GetGlobalIRC())
153 res = GetGlobalIRC()->sendChat(sText.substr(sText.find(' ') + 1));
154 if (!res)
155 client->cChatbox.AddText("Could not send the IRC message", tLX->clNetworkText, TXT_NETWORK, tLX->currentTime);
156 return;
157 }
158
159 // We can safely send long messages to servers >= beta8
160 if (client->getServerVersion() >= OLXBetaVersion(8)) {
161
162 // HTML support since beta 8
163 SendTextInternal(OldLxCompatibleString(sText), sWormName);
164
165
166 // <=beta7 server
167 } else {
168
169 if (chat_command &&
170 client->getServerVersion() < OLXBetaVersion(3) && // <beta3 clients don't have chat command support
171 sText.find("/me ") != 0) // Ignores "/me" special command
172 {
173 // Try if we can execute the same command in console (mainly for "/suicide" command to work on all servers)
174 if( !subStrCaseEqual( sText.substr(1), "suicide", 7 ) )
175 Con_Execute(sText.substr(1));
176 else
177 client->cChatbox.AddText("HINT: server cannot execute commands, only OLX beta3+ can", tLX->clNotice, TXT_NOTICE, tLX->currentTime);
178
179 return;
180
181 } else if (chat_command &&
182 client->getServerVersion() >= OLXBetaVersion(3)) {
183 // we don't have to split chat commands
184 // "/me ..." is also save, because server uses SendGlobalText for sending and it splits the text there
185 SendTextInternal(OldLxCompatibleString(sText), sWormName);
186
187 } else { // we can savely split the message (and we have to)
188
189 // If the text is too long, split it in smaller pieces and then send (backward comaptibility)
190 int name_w = tLX->cFont.GetWidth(sWormName + ": ");
191 std::vector<std::string> split = splitstring(StripHtmlTags(sText), 63,
192 client->iNetStatus == NET_CONNECTED ? 600 - name_w : 300 - name_w, tLX->cFont);
193
194 // Check
195 if (split.size() == 0) { // More than weird...
196 SendTextInternal(OldLxCompatibleString(StripHtmlTags(sText)), sWormName);
197 return;
198 }
199
200 // Send the first chunk
201 SendTextInternal(OldLxCompatibleString(split[0]), sWormName);
202
203 // Send the text
204 for (std::vector<std::string>::const_iterator it = split.begin() + 1; it != split.end(); it++)
205 SendTextInternal(OldLxCompatibleString(*it), "");
206 }
207 }
208 }
209
SendChatCommandCompletionRequest(const std::string & startStr)210 void CClientNetEngineBeta7::SendChatCommandCompletionRequest(const std::string& startStr) {
211 CBytestream bs;
212 bs.writeByte(C2S_CHATCMDCOMPLREQ);
213 bs.writeString(startStr);
214
215 client->cNetChan->AddReliablePacketToSend(bs);
216 }
217
SendAFK(int wormid,AFK_TYPE afkType,const std::string & message)218 void CClientNetEngineBeta7::SendAFK(int wormid, AFK_TYPE afkType, const std::string & message ) {
219 if( client->getServerVersion() < OLXBetaVersion(7) )
220 return;
221
222 std::string msg = message;
223 if( msg == "" )
224 {
225 switch(afkType) {
226 case AFK_BACK_ONLINE: break;
227 case AFK_TYPING_CHAT: msg = "(typing)"; break;
228 case AFK_AWAY: msg = "(away)"; break;
229 case AFK_SELECTING_WPNS: msg = "(selecting weapons)"; break;
230 case AFK_CONSOLE: msg = "(console)"; break;
231 case AFK_MENU: msg = "(menu)"; break;
232 }
233 }
234
235
236 for( int i=0; i < client->getNumWorms(); i++ )
237 if( client->getWorm(i)->getID() == wormid )
238 client->getWorm(i)->setAFK(afkType, msg);
239
240 CBytestream bs;
241 bs.writeByte(C2S_AFK);
242 bs.writeByte(wormid);
243 bs.writeByte(afkType);
244 if(msg.size() > 127)
245 msg = msg.substr(0, 127);
246 bs.writeString(msg);
247
248 client->cNetChan->AddReliablePacketToSend(bs);
249 }
250
251 /////////////////////
252 // Internal function for text sending, does not do any checks or parsing
SendTextInternal(const std::string & sText,const std::string & sWormName)253 void CClientNetEngine::SendTextInternal(const std::string& sText, const std::string& sWormName)
254 {
255 if(!client) {
256 errors << "CClientNetEngine::SendTextInternal(" << sWormName << ": " << sText << "): my client is unset" << endl;
257 return;
258 }
259
260 if(!client->cNetChan) {
261 errors << "CClientNetEngine::SendTextInternal(" << sWormName << ": " << sText << "): cNetChan of my client (" << client->debugName() << ") is unset" << endl;
262 return;
263 }
264
265 CBytestream bs;
266 bs.writeByte(C2S_CHATTEXT);
267 if (sWormName.size() == 0)
268 bs.writeString(sText);
269 else if( sText.find("/me ") == 0 && client->getServerVersion() < OLXBetaVersion(0,58,1) ) // "/me " chat command
270 bs.writeString( "* " + sWormName + " " + sText.substr(4)); // Add star so clients with empty name won't fake others
271 else // "/me " command is server-sided on Beta9
272 bs.writeString(sWormName + ": " + sText);
273
274 client->cNetChan->AddReliablePacketToSend(bs);
275 }
276
277 #ifdef FUZZY_ERROR_TESTING
278 //////////////////
279 // Send a random packet to server (used for debugging)
SendRandomPacket()280 void CClientNetEngine::SendRandomPacket()
281 {
282 // don't send random packets from the local client to our own server
283 if( tGameInfo.iGameType != GME_JOIN ) return;
284
285 CBytestream bs;
286
287 int random_length = GetRandomInt(50);
288 for (int i=0; i < random_length; i++)
289 bs.writeByte((uchar)GetRandomInt(255));
290
291 client->cNetChan->AddReliablePacketToSend(bs);
292
293 bs.Clear();
294
295 // Test connectionless packets
296 if (GetRandomInt(100) >= 75) {
297 bs.writeInt(-1, 4);
298 static const std::string commands[] = { "lx::getchallenge", "lx::connect", "lx::ping", "lx::query",
299 "lx::getinfo", "lx::wantsjoin", "lx::traverse", "lx::registered"};
300 bs.writeString(commands[GetRandomInt(500) % (sizeof(commands)/sizeof(std::string))]);
301 for (int i=0; i < random_length; i++)
302 bs.writeByte((uchar)GetRandomInt(255));
303 SetRemoteNetAddr(client->tSocket, client->cNetChan->getAddress());
304 bs.Send(client->tSocket);
305 }
306 }
307 #endif
308
SendGrabBonus(int id,int wormid)309 void CClientNetEngine::SendGrabBonus(int id, int wormid)
310 {
311 CBytestream bs;
312 bs.writeByte(C2S_GRABBONUS);
313 bs.writeByte(id);
314 bs.writeByte(wormid);
315 bs.writeByte(client->cRemoteWorms[wormid].getCurrentWeapon());
316
317 client->cNetChan->AddReliablePacketToSend(bs);
318 }
319
SendUpdateLobby(bool ready)320 void CClientNetEngine::SendUpdateLobby(bool ready)
321 {
322 CBytestream bs;
323 bs.writeByte(C2S_UPDATELOBBY);
324 bs.writeByte(ready);
325 client->getChannel()->AddReliablePacketToSend(bs);
326 }
327
SendDisconnect()328 void CClientNetEngine::SendDisconnect()
329 {
330 CBytestream bs;
331
332 bs.writeByte(C2S_DISCONNECT);
333
334 // Send the pack a few times to make sure the server gets the packet
335 if( client->cNetChan != NULL)
336 for(int i=0;i<3;i++)
337 client->cNetChan->Transmit(&bs);
338 }
339
SendFileData()340 void CClientNetEngine::SendFileData()
341 {
342 CBytestream bs;
343 bs.writeByte(C2S_SENDFILE);
344 client->getUdpFileDownloader()->send(&bs);
345 client->getChannel()->AddReliablePacketToSend(bs);
346 }
347
QueueReportDamage(int victim,float damage,int offender)348 void CClientNetEngineBeta9::QueueReportDamage(int victim, float damage, int offender)
349 {
350 // Buffer up all damage and send it once per 0.1 second for LAN nettype, or once per 0.3 seconds for modem
351 std::pair< int, int > dmgPair = std::make_pair( victim, offender );
352 if( cDamageReport.count( dmgPair ) == 0 )
353 cDamageReport[ dmgPair ] = 0;
354 cDamageReport[ dmgPair ] += damage;
355
356 SendReportDamage();
357 }
358
SendReportDamage(bool flush)359 void CClientNetEngineBeta9::SendReportDamage(bool flush)
360 {
361 if( ! flush && tLX->currentTime - fLastDamageReportSent < 0.1f * ( NST_LOCAL - client->getNetSpeed() ) )
362 return;
363
364 CBytestream bs;
365
366 for( std::map< std::pair< int, int >, float > :: iterator it = cDamageReport.begin();
367 it != cDamageReport.end(); it++ )
368 {
369 int victim = it->first.first;
370 int offender = it->first.second;
371 float damage = it->second;
372 bs.writeByte(C2S_REPORTDAMAGE);
373 bs.writeByte(victim);
374 bs.writeFloat(damage);
375 bs.writeByte(offender);
376 }
377
378 client->cNetChan->AddReliablePacketToSend(bs);
379
380 cDamageReport.clear();
381 fLastDamageReportSent = tLX->currentTime;
382 }
383