1 /* bzflag
2 * Copyright (c) 1993-2021 Tim Riker
3 *
4 * This package is free software; you can redistribute it and/or
5 * modify it under the terms of the license found in the file
6 * named COPYING that should have accompanied this file.
7 *
8 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
9 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
10 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
11 */
12
13 /* interface header */
14 #include "PlayerInfo.h"
15
16 /* system implementation headers */
17 #include <errno.h>
18 #include <stdio.h>
19 #include <string>
20 #include <ctype.h>
21
22 /* implementation-specific common headers */
23 #include "TextUtils.h"
24
25 WordFilter PlayerInfo::serverSpoofingFilter;
26 TimeKeeper PlayerInfo::now = TimeKeeper::getCurrent();
27
28 bool PlayerInfo::callSignFiltering = false;
29 WordFilter *PlayerInfo::filterData = NULL;
30 bool PlayerInfo::simpleFiltering = true;
31
PlayerInfo(int _playerIndex)32 PlayerInfo::PlayerInfo(int _playerIndex) :
33 playerIndex(_playerIndex),
34 state(PlayerInLimbo),
35 hasDoneEntering(false),
36 team(NoTeam),
37 flag(-1),
38 allowedToSpawn(true),
39 notifiedSpawn(false),
40 spamWarns(0),
41 lastMsgTime(now),
42 paused(false),
43 pausedSince(TimeKeeper::getNullTime()),
44 autopilot(false),
45 tracker(0)
46 {
47 notResponding = false;
48 memset(motto, 0, MottoLen);
49 memset(callSign, 0, CallSignLen);
50 memset(token, 0, TokenLen);
51 memset(clientVersion, 0, VersionLen);
52 clientVersionMajor = -1;
53 clientVersionMinor = -1;
54 clientVersionRevision = -1;
55 completelyAdded = false;
56 nextSpawnTime = TimeKeeper::getSunGenesisTime();
57 wantsToSpawn = false;
58 neverSpawned = true;
59 type = TankPlayer;
60 }
61
setFilterParameters(bool _callSignFiltering,WordFilter & _filterData,bool _simpleFiltering)62 void PlayerInfo::setFilterParameters(bool _callSignFiltering,
63 WordFilter &_filterData,
64 bool _simpleFiltering)
65 {
66 callSignFiltering = _callSignFiltering;
67 filterData = &_filterData;
68 simpleFiltering = _simpleFiltering;
69 }
70
resetPlayer(bool ctf)71 void PlayerInfo::resetPlayer(bool ctf)
72 {
73 wasRabbit = false;
74
75 lastupdate = now;
76 lastmsg = now;
77
78 replayState = ReplayNone;
79
80 playedEarly = false;
81
82 restartOnBase = ctf;
83 }
84
setRestartOnBase(bool on)85 void PlayerInfo::setRestartOnBase(bool on)
86 {
87 restartOnBase = on;
88 }
89
shouldRestartAtBase()90 bool PlayerInfo::shouldRestartAtBase()
91 {
92 return restartOnBase;
93 }
94
signingOn()95 void PlayerInfo::signingOn()
96 {
97 state = PlayerDead;
98 }
99
setAlive()100 void PlayerInfo::setAlive()
101 {
102 state = PlayerAlive;
103 paused = false;
104 wantsToSpawn = false;
105 neverSpawned = false;
106 flag = -1;
107 }
108
setDead()109 void PlayerInfo::setDead()
110 {
111 state = PlayerDead;
112 }
113
packUpdate(void * buf)114 void *PlayerInfo::packUpdate(void *buf)
115 {
116 buf = nboPackUShort(buf, uint16_t(type));
117 buf = nboPackUShort(buf, uint16_t(team));
118 return buf;
119 }
120
packId(void * buf)121 void *PlayerInfo::packId(void *buf)
122 {
123 buf = nboPackString(buf, callSign, CallSignLen);
124 buf = nboPackString(buf, motto, MottoLen);
125 return buf;
126 }
127
unpackEnter(const void * buf,uint16_t & rejectCode,char * rejectMsg)128 bool PlayerInfo::unpackEnter(const void *buf, uint16_t &rejectCode, char *rejectMsg)
129 {
130 // data: type, team, name, motto
131 uint16_t _type;
132 int16_t _team;
133 buf = nboUnpackUShort(buf, _type);
134 buf = nboUnpackShort(buf, _team);
135 type = PlayerType(_type);
136 team = TeamColor(_team);
137 buf = nboUnpackString(buf, callSign, CallSignLen);
138 buf = nboUnpackString(buf, motto, MottoLen);
139 buf = nboUnpackString(buf, token, TokenLen);
140 buf = nboUnpackString(buf, clientVersion, VersionLen);
141
142 return processEnter(rejectCode, rejectMsg);
143 }
144
processEnter(uint16_t & rejectCode,char * rejectMsg)145 bool PlayerInfo::processEnter ( uint16_t &rejectCode, char *rejectMsg )
146 {
147 // terminate the strings
148 callSign[CallSignLen - 1] = '\0';
149 motto[MottoLen - 1] = '\0';
150 token[TokenLen - 1] = '\0';
151 clientVersion[VersionLen - 1] = '\0';
152 cleanMotto();
153
154 logDebugMessage(2,"Player %s [%d] sent version string: %s\n",
155 callSign, playerIndex, clientVersion);
156 int major, minor, rev;
157 if (sscanf(clientVersion, "%d.%d.%d", &major, &minor, &rev) == 3)
158 {
159 clientVersionMajor = major;
160 clientVersionMinor = minor;
161 clientVersionRevision = rev;
162 }
163 logDebugMessage(4,"Player %s version code parsed as: %i.%i.%i\n", callSign,
164 clientVersionMajor, clientVersionMinor, clientVersionRevision);
165
166 // spoof filter holds "SERVER" for robust name comparisons
167 if (serverSpoofingFilter.wordCount() == 0)
168 serverSpoofingFilter.addToFilter("SERVER", "");
169
170 if (isBot() && BZDB.isTrue(StateDatabase::BZDB_DISABLEBOTS))
171 {
172 logDebugMessage(2,"rejecting robot tank: %s\n", callSign);
173 rejectCode = RejectBadType;
174 strcpy(rejectMsg, "Robot tanks are not allowed on this server.");
175 return false;
176 }
177
178 if (!isCallSignReadable())
179 {
180 logDebugMessage(2,"rejecting unreadable callsign: %s\n", callSign);
181 rejectCode = RejectBadCallsign;
182 strcpy(rejectMsg, errorString.c_str());
183 return false;
184 }
185 // no spoofing the server name
186 if (serverSpoofingFilter.filter(callSign))
187 {
188 rejectCode = RejectRepeatCallsign;
189 strcpy(rejectMsg, "The callsign specified is already in use.");
190 return false;
191 }
192 if (!isMottoReadable())
193 {
194 logDebugMessage(2,"ignoring unreadable player motto: %s (%s)\n", callSign, motto);
195 // The sendMessage() function is not available in this scope.
196 // TODO: reorganize the code to send this courtesy notice to the player.
197 // sendMessage(ServerPlayer, playerIndex, "Ignoring your unreadable motto.");
198 setMotto("");
199 }
200
201 // make sure the callsign is not obscene/filtered
202 if (callSignFiltering)
203 {
204 logDebugMessage(2,"checking callsign: %s\n",callSign);
205
206 char cs[CallSignLen];
207 memcpy(cs, callSign, sizeof(char) * CallSignLen);
208 if (filterData->filter(cs, simpleFiltering))
209 {
210 rejectCode = RejectBadCallsign;
211 strcpy(rejectMsg,
212 "The callsign was rejected. Try a different callsign.");
213 return false;
214 }
215 }
216
217 // make sure the motto is not obscene/filtered
218 if (callSignFiltering)
219 {
220 logDebugMessage(2,"checking motto: %s\n", motto);
221 char em[MottoLen];
222 memcpy(em, motto, sizeof(char) * MottoLen);
223 if (filterData->filter(em, simpleFiltering))
224 {
225 rejectCode = RejectBadMotto;
226 strcpy(rejectMsg, "The motto was rejected. Try a different motto.");
227 return false;
228 }
229 }
230
231 if (token[0] == 0)
232 strcpy(token, "NONE");
233 hasDoneEntering = true;
234 return true;
235 }
236
getCallSign() const237 const char *PlayerInfo::getCallSign() const
238 {
239 return callSign;
240 }
241
setCallSign(const char * c)242 void PlayerInfo::setCallSign(const char * c)
243 {
244 if (c != NULL)
245 {
246 strncpy(callSign, c, CallSignLen);
247 callSign[CallSignLen - 1] = '\0'; // ensure null termination
248 }
249 }
250
251
isCallSignReadable()252 bool PlayerInfo::isCallSignReadable()
253 {
254 // callsign readability filter, make sure there are more alphanum than non
255 // keep a count of alpha-numerics
256
257 int callsignlen = (int)strlen(callSign);
258 // reject less than 2 characters
259 if (callsignlen < 2)
260 {
261 errorString = "Callsigns must be at least 2 characters.";
262 return false;
263 }
264
265 // reject trailing space
266 if (isspace(callSign[strlen(callSign) - 1]))
267 {
268 errorString = "Trailing spaces are not allowed in callsigns.";
269 return false;
270 }
271
272 // prevent spoofing global login indicators + and @ in the scoreboard
273 // and reserve # for /kick or /ban #slot
274 if (*callSign=='+' || *callSign=='@' || *callSign=='#' )
275 {
276 errorString = "Callsigns are not allowed to start with +, @, or #.";
277 return false;
278 }
279
280 // start with true to reject leading space
281 bool lastWasSpace = true;
282 int alnumCount = 0;
283 const char *sp = callSign;
284 do
285 {
286 // reject sequential spaces
287 if (lastWasSpace && isspace(*sp))
288 {
289 errorString = "Leading or consecutive spaces are not allowed in callsigns.";
290 return false;
291 }
292
293 // reject ' and " and any nonprintable
294 if ((*sp == '\'') || (*sp == '"') || ((unsigned)*sp > 0x7f) || !isprint(*sp))
295 {
296 errorString = "Non-printable characters and quotes are not allowed in callsigns.";
297 return false;
298 }
299 if (isspace(*sp))
300 {
301 // only space is valid, not tab etc.
302 if (*sp != ' ')
303 {
304 errorString = "Invalid whitespace in callsign.";
305 return false;
306 }
307 lastWasSpace = true;
308 }
309 else
310 {
311 lastWasSpace = false;
312 if (isalnum(*sp))
313 alnumCount++;
314 }
315 }
316 while (*++sp);
317
318 bool readable = ((float)alnumCount / (float)callsignlen) > 0.5f;
319 if (!readable)
320 errorString = "Callsign rejected. Please use mostly letters and numbers.";
321 return readable;
322 }
323
getMotto() const324 const char *PlayerInfo::getMotto() const
325 {
326 return motto;
327 }
328
cleanMotto()329 void PlayerInfo::cleanMotto()
330 {
331 // strip leading whitespace from motto
332 char *sp = motto;
333 char *tp = sp;
334 while (isspace(*sp))
335 sp++;
336
337 // strip any non-printable characters and ' and " from motto
338 do
339 {
340 if (isprint(*sp) && (*sp != '\'') && (*sp != '"'))
341 *tp++ = *sp;
342 }
343 while (*++sp);
344 *tp = *sp;
345
346 // strip trailing whitespace from motto
347 while (isspace(*--tp))
348 *tp=0;
349 }
350
isMottoReadable()351 bool PlayerInfo::isMottoReadable()
352 {
353 // motto/"team" readability filter, make sure there are more
354 // alphanum than non
355 int mottoAlnumCount = 0;
356 char *sp = motto;
357 do
358 {
359 if (isalnum(*sp))
360 mottoAlnumCount++;
361 }
362 while (*++sp);
363 int mottolen = (int)strlen(motto);
364 return (mottolen <= 4) || (((float)mottoAlnumCount / (float)mottolen) > 0.5);
365 }
366
setMotto(const char * _motto)367 void PlayerInfo::setMotto(const char* _motto)
368 {
369 strncpy(motto, _motto, MottoLen);
370 motto[MottoLen - 1] = '\0'; // ensure null termination
371 }
372
getToken() const373 const char *PlayerInfo::getToken() const
374 {
375 return token;
376 }
377
setToken(const char * c)378 void PlayerInfo::setToken(const char * c)
379 {
380 if (c != NULL)
381 {
382 strncpy(token, c, TokenLen);
383 token[TokenLen - 1] = '\0'; // ensure null termination
384 }
385 }
386
clearToken()387 void PlayerInfo::clearToken()
388 {
389 token[0] = '\0';
390 }
391
packVirtualFlagCapture(void * buf)392 void *PlayerInfo::packVirtualFlagCapture(void *buf)
393 {
394 buf = nboPackUShort(buf, uint16_t(int(team) - 1));
395 buf = nboPackUShort(buf, uint16_t(1 + (int(team) % 4)));
396 return buf;
397 }
398
isTeam(TeamColor _team) const399 bool PlayerInfo::isTeam(TeamColor _team) const
400 {
401 return team == _team;
402 }
403
isObserver() const404 bool PlayerInfo::isObserver() const
405 {
406 return team == ObserverTeam;
407 }
408
getTeam() const409 TeamColor PlayerInfo::getTeam() const
410 {
411 return team;
412 }
413
setTeam(TeamColor _team)414 void PlayerInfo::setTeam(TeamColor _team)
415 {
416 team = _team;
417 }
418
wasARabbit()419 void PlayerInfo::wasARabbit()
420 {
421 team = HunterTeam;
422 wasRabbit = true;
423 }
424
wasNotARabbit()425 void PlayerInfo::wasNotARabbit()
426 {
427 wasRabbit = false;
428 }
429
resetFlag()430 void PlayerInfo::resetFlag()
431 {
432 flag = -1;
433 lastFlagDropTime = now;
434 }
435
setFlag(int _flag)436 void PlayerInfo::setFlag(int _flag)
437 {
438 flag = _flag;
439 }
440
isFlagTransitSafe()441 bool PlayerInfo::isFlagTransitSafe()
442 {
443 return now - lastFlagDropTime >= 2.0f;
444 }
445
timeSinceLastFlagDrop()446 double PlayerInfo::timeSinceLastFlagDrop()
447 {
448 return now - lastFlagDropTime;
449 }
450
getClientVersion()451 const char *PlayerInfo::getClientVersion()
452 {
453 return clientVersion;
454 }
455
setClientVersion(const char * c)456 void PlayerInfo::setClientVersion(const char * c)
457 {
458 if (c != NULL)
459 {
460 strncpy(clientVersion, c, VersionLen);
461 clientVersion[VersionLen - 1] = '\0'; // ensure null termination
462 }
463 }
464
getClientVersionNumbers(int & major,int & minor,int & rev)465 void PlayerInfo::getClientVersionNumbers(int& major, int& minor, int& rev)
466 {
467 major = clientVersionMajor;
468 minor = clientVersionMinor;
469 rev = clientVersionRevision;
470 return;
471 }
472
getPausedTime()473 double PlayerInfo::getPausedTime()
474 {
475 if ((state > PlayerInLimbo) && (team != ObserverTeam) && (paused))
476 return (now - pausedSince);
477
478 return 0;
479 }
480
getIdleTime()481 double PlayerInfo::getIdleTime()
482 {
483 if ((state > PlayerInLimbo) && (team != ObserverTeam))
484 return (now - lastupdate);
485 else if (team == ObserverTeam)
486 return -1;
487
488 return 0;
489 }
490
getIdleStat()491 std::string PlayerInfo::getIdleStat()
492 {
493 std::string reply;
494 if ((state > PlayerInLimbo) && (team != ObserverTeam))
495 {
496 reply = TextUtils::format("%s\t: %4ds", callSign,
497 int(now - lastupdate));
498 if (paused)
499 reply += TextUtils::format(" paused %4ds", int(now - pausedSince));
500 }
501 return reply;
502 }
503
canBeRabbit(bool relaxing)504 bool PlayerInfo::canBeRabbit(bool relaxing)
505 {
506 if (paused || notResponding || (team == ObserverTeam))
507 return false;
508 return relaxing ? (state > PlayerInLimbo) : (state == PlayerAlive);
509 }
510
setPaused(bool _paused)511 void PlayerInfo::setPaused(bool _paused)
512 {
513 paused = _paused;
514 pausedSince = now;
515 }
516
setAutoPilot(bool _autopilot)517 void PlayerInfo::setAutoPilot(bool _autopilot)
518 {
519 autopilot = _autopilot;
520 }
521
isTooMuchIdling(float kickThresh)522 bool PlayerInfo::isTooMuchIdling(float kickThresh)
523 {
524 bool idling = false;
525 if ((state > PlayerInLimbo) && (team != ObserverTeam))
526 {
527 const float idletime = (float)(now - lastupdate);
528 if (idletime > kickThresh)
529 idling = true;
530 }
531 return idling;
532 }
533
hasStartedToNotRespond()534 bool PlayerInfo::hasStartedToNotRespond()
535 {
536 const float notRespondingTime =
537 BZDB.eval(StateDatabase::BZDB_NOTRESPONDINGTIME);
538 bool startingToNotRespond = false;
539 if (state > PlayerInLimbo)
540 {
541 bool oldnr = notResponding;
542 notResponding = (now - lastupdate) > notRespondingTime;
543 if (!oldnr && notResponding)
544 startingToNotRespond = true;
545 }
546 return startingToNotRespond;
547 }
548
hasSent()549 void PlayerInfo::hasSent()
550 {
551 lastmsg = now;
552 }
553
hasPlayedEarly()554 bool PlayerInfo::hasPlayedEarly()
555 {
556 bool returnValue = playedEarly;
557 playedEarly = false;
558 return returnValue;
559 }
560
setPlayedEarly(bool early)561 void PlayerInfo::setPlayedEarly(bool early)
562 {
563 playedEarly = early;
564 }
565
updateIdleTime()566 void PlayerInfo::updateIdleTime()
567 {
568 if (!paused && (state != PlayerDead))
569 lastupdate = now;
570 }
571
setReplayState(PlayerReplayState _state)572 void PlayerInfo::setReplayState(PlayerReplayState _state)
573 {
574 replayState = _state;
575 }
576
getReplayState()577 PlayerReplayState PlayerInfo::getReplayState()
578 {
579 return replayState;
580 }
581
582
setTrackerID(unsigned short int t)583 void PlayerInfo::setTrackerID(unsigned short int t)
584 {
585 tracker = t;
586 }
587
588
trackerID()589 unsigned short int PlayerInfo::trackerID()
590 {
591 return tracker;
592 }
593
setCurrentTime(TimeKeeper tm)594 void PlayerInfo::setCurrentTime(TimeKeeper tm)
595 {
596 now = tm;
597 }
598
killedBy(PlayerId killer)599 void PlayerInfo::killedBy(PlayerId killer)
600 {
601 if (deathCountMap.find(killer) == deathCountMap.end())
602 deathCountMap[killer] = 0;
603 deathCountMap[killer]++;
604 }
605
flushKiller(PlayerId killer)606 void PlayerInfo::flushKiller(PlayerId killer)
607 {
608 if (deathCountMap.find(killer) != deathCountMap.end())
609 deathCountMap.erase(killer);
610 }
611
howManyTimesKilledBy(PlayerId killer)612 int PlayerInfo::howManyTimesKilledBy(PlayerId killer)
613 {
614 if (deathCountMap.find(killer) == deathCountMap.end())
615 return 0;
616 return deathCountMap[killer];
617 }
618
setAllowedToSpawn(bool canSpawn)619 void PlayerInfo::setAllowedToSpawn(bool canSpawn)
620 {
621 allowedToSpawn = canSpawn;
622 }
623
setNotifiedOfSpawnable(bool notified)624 void PlayerInfo::setNotifiedOfSpawnable(bool notified)
625 {
626 notifiedSpawn = notified;
627 }
628
629 // Local Variables: ***
630 // mode: C++ ***
631 // tab-width: 4 ***
632 // c-basic-offset: 4 ***
633 // indent-tabs-mode: nil ***
634 // End: ***
635 // ex: shiftwidth=4 tabstop=4
636