1 // WARNING: Before modifying this file, please read our Guidelines 2 // This file can be found in the distribution under: ./docs/guidelines.txt 3 // Or at: http://redeclipse.net/wiki/Multiplayer_Guidelines 4 // 5 // The Red Eclipse Team provides the play.redeclipse.net master server for the 6 // benefit of the Red Eclipse Community. We impose a general set of guidelines 7 // for any server/user which connects to the play.redeclipse.net master server. 8 // The team reserves the right to block any attempt to connect to the master 9 // server at their discretion. Access to services provided by the project are 10 // considered to be a privilege, not a right. 11 12 // These guidelines are imposed to ensure the integrity of both the Red Eclipse 13 // game, and its community. If you do not agree to these terms, you should not 14 // connect to the play.redeclipse.net master server, or any servers which are 15 // connected to it. These guidelines are not designed to limit your opinion or 16 // freedoms granted to you by the open source licenses employed by the project, 17 // nor do they cover usage of the game in either offline play or on servers 18 // which are not connected to the Red Eclipse master. 19 20 // If you have questions or comments regarding these guidelines please contact 21 // the Red Eclipse Team. Any person seeking to modify their game or server for 22 // use on the master server should first seek permission from the Red Eclipse 23 // Team, each modification must be approved and will be done on a case-by-case 24 // basis. 25 26 #define GAMESERVER 1 27 #include "game.h" 28 #include "errno.h" 29 30 namespace server 31 { 32 struct srventity 33 { 34 vec o; 35 int type; 36 bool spawned; 37 int millis, last; 38 attrvector attrs, kin; 39 srventityserver::srventity40 srventity() : o(0, 0, 0), type(NOTUSED), spawned(false), millis(0), last(0) { reset(); } ~srventityserver::srventity41 ~srventity() { reset(); } 42 resetserver::srventity43 void reset() 44 { 45 o = vec(0, 0, 0); 46 attrs.shrink(0); 47 kin.shrink(0); 48 } 49 }; 50 51 static const int DEATHMILLIS = 300; 52 53 struct clientinfo; 54 55 struct gameevent 56 { ~gameeventserver::gameevent57 virtual ~gameevent() {} 58 virtual bool flush(clientinfo *ci, int fmillis); processserver::gameevent59 virtual void process(clientinfo *ci) {} keepableserver::gameevent60 virtual bool keepable() const { return false; } 61 }; 62 63 struct timedevent : gameevent 64 { 65 int millis; 66 bool flush(clientinfo *ci, int fmillis); 67 }; 68 69 struct shotevent : timedevent 70 { 71 int id, weap, flags, scale, num; 72 ivec from; 73 vector<shotmsg> shots; 74 void process(clientinfo *ci); 75 }; 76 77 struct switchevent : timedevent 78 { 79 int id, weap; 80 void process(clientinfo *ci); 81 }; 82 83 struct dropevent : timedevent 84 { 85 int id, weap; 86 void process(clientinfo *ci); 87 }; 88 89 struct reloadevent : timedevent 90 { 91 int id, weap; 92 void process(clientinfo *ci); 93 }; 94 95 struct hitset 96 { 97 int flags, proj, target; 98 union 99 { 100 int rays; 101 int dist; 102 }; 103 ivec dir, vel; 104 }; 105 106 struct destroyevent : timedevent 107 { 108 int id, weap, fromweap, fromflags, flags, radial, scale; 109 vector<hitset> hits; keepableserver::destroyevent110 bool keepable() const { return true; } 111 void process(clientinfo *ci); 112 }; 113 114 struct suicideevent : gameevent 115 { 116 int flags, material; 117 void process(clientinfo *ci); 118 }; 119 120 struct useevent : timedevent 121 { 122 int id, ent; 123 void process(clientinfo *ci); 124 }; 125 126 struct stickyevent : timedevent 127 { 128 int id, weap, flags, target; 129 ivec norm, pos; keepableserver::stickyevent130 bool keepable() const { return true; } 131 void process(clientinfo *ci); 132 }; 133 134 struct projectile 135 { 136 int id, ammo; projectileserver::projectile137 projectile(int n, int a) : id(n), ammo(a) {} ~projectileserver::projectile138 ~projectile() {} 139 }; 140 struct projectilestate 141 { 142 vector<projectile> projs; projectilestateserver::projectilestate143 projectilestate() { reset(); } resetserver::projectilestate144 void reset() { projs.shrink(0); } addserver::projectilestate145 void add(int id, int ammo = -1) 146 { 147 projs.add(projectile(id, ammo)); 148 } removeserver::projectilestate149 bool remove(int id) 150 { 151 loopv(projs) if(projs[i].id==id) 152 { 153 projs.remove(i); 154 return true; 155 } 156 return false; 157 } removeallserver::projectilestate158 int removeall(int id) 159 { 160 int count = 0; 161 loopvrev(projs) if(projs[i].id==id) 162 { 163 projs.remove(i); 164 count++; 165 } 166 return count; 167 } findserver::projectilestate168 bool find(int id) 169 { 170 loopv(projs) if(projs[i].id==id) return true; 171 return false; 172 } valuesserver::projectilestate173 void values(int id, int &a) 174 { 175 loopv(projs) if(projs[i].id==id) { a = projs[i].ammo; return; } 176 a = -1; 177 } 178 }; 179 180 struct dmghist 181 { 182 int clientnum, millis; dmghistserver::dmghist183 dmghist() {} dmghistserver::dmghist184 dmghist(int c, int m) : clientnum(c), millis(m) {} ~dmghistserver::dmghist185 ~dmghist() {} 186 }; 187 188 struct teamkill 189 { 190 int millis, team, points; teamkillserver::teamkill191 teamkill() {} teamkillserver::teamkill192 teamkill(int m, int t, int p) : millis(m), team(t), points(p) {} ~teamkillserver::teamkill193 ~teamkill() {} 194 }; 195 196 struct weaponstats 197 { 198 int timewielded, timeloadout; 199 int hits1, hits2, flakhits1, flakhits2; 200 int shots1, shots2, flakshots1, flakshots2; 201 int frags1, frags2, damage1, damage2; 202 weaponstatsserver::weaponstats203 weaponstats() { reset(); } ~weaponstatsserver::weaponstats204 ~weaponstats() {} 205 resetserver::weaponstats206 void reset() 207 { 208 timewielded = timeloadout = 0; 209 hits1 = hits2 = flakhits1 = flakhits2 = 0; 210 shots1 = shots2 = flakshots1 = flakshots2 = 0; 211 frags1 = frags2 = damage1 = damage2 = 0; 212 } 213 }; 214 215 struct capturestats 216 { 217 int capturing; 218 int captured; 219 capturestatsserver::capturestats220 capturestats() { reset(); } ~capturestatsserver::capturestats221 ~capturestats() {} 222 resetserver::capturestats223 void reset() 224 { 225 capturing = captured = 0; 226 } 227 }; 228 229 struct bombstats 230 { 231 int bombing; 232 int bombed; 233 bombstatsserver::bombstats234 bombstats() { reset(); } ~bombstatsserver::bombstats235 ~bombstats() {} 236 resetserver::bombstats237 void reset() 238 { 239 bombing = bombed = 0; 240 } 241 }; 242 243 struct ffaroundstats 244 { 245 int round; 246 bool winner; 247 ffaroundstatsserver::ffaroundstats248 ffaroundstats() { reset(); } ~ffaroundstatsserver::ffaroundstats249 ~ffaroundstats() {} 250 resetserver::ffaroundstats251 void reset() 252 { 253 round = 0; 254 winner = false; 255 } 256 }; 257 258 extern int gamemode, mutators; 259 260 enum { WARN_CHAT = 0, WARN_TEAMKILL, WARN_MAX }; 261 262 struct servstate : baseent, clientstate 263 { 264 int rewards[2], shotdamage, damage, lasttimewielded, lasttimeloadout[W_MAX], aireinit, 265 lastresowner[WR_MAX], lasttimealive, timealive, lasttimeactive, timeactive, lastresweapon[WR_MAX], lasthurt, 266 localtotalpoints, localtotalfrags, localtotaldeaths; 267 bool lastresalt[W_MAX]; 268 projectilestate dropped, weapshots[W_MAX][2]; 269 vector<int> fraglog, fragmillis, cpnodes, chatmillis; 270 vector<dmghist> damagelog; 271 vector<teamkill> teamkills; 272 273 weaponstats weapstats[W_MAX]; 274 vector<capturestats> captures; 275 vector<bombstats> bombings; 276 vector<ffaroundstats> ffarounds; 277 278 int warnings[WARN_MAX][2]; 279 servstateserver::servstate280 servstate() : lasttimewielded(0), aireinit(0), lasttimealive(0), timealive(0), lasttimeactive(0), timeactive(0), lasthurt(0), localtotalpoints(0), localtotalfrags(0), localtotaldeaths(0) 281 { 282 loopi(WARN_MAX) loopj(2) warnings[i][j] = 0; 283 loopi(W_MAX) lasttimeloadout[i] = 0; 284 resetresidualowner(); 285 } 286 isaliveserver::servstate287 bool isalive(int millis) 288 { 289 return state == CS_ALIVE || ((state == CS_DEAD || state == CS_WAITING) && lastdeath && millis-lastdeath <= DEATHMILLIS); 290 } 291 mapchangeserver::servstate292 void mapchange(bool change = false) 293 { 294 if(state != CS_SPECTATOR) state = CS_DEAD; 295 dropped.reset(); 296 loopi(W_MAX) loopj(2) weapshots[i][j].reset(); 297 clientstate::mapchange(change); 298 rewards[0] = rewards[1] = shotdamage = damage = timealive = timeactive = lasthurt = 0; 299 fraglog.shrink(0); 300 fragmillis.shrink(0); 301 cpnodes.shrink(0); 302 damagelog.shrink(0); 303 teamkills.shrink(0); 304 loopi(W_MAX) weapstats[i].reset(); 305 captures.shrink(0); 306 bombings.shrink(0); 307 ffarounds.shrink(0); 308 respawn(0); 309 } 310 resetresidualownerserver::servstate311 void resetresidualowner(int n = -1) 312 { 313 if(n >= 0 && n < WR_MAX) lastresowner[n] = -1; 314 else loopi(WR_MAX) lastresowner[i] = -1; 315 } 316 respawnserver::servstate317 void respawn(int millis) 318 { 319 baseent::reset(); 320 rewards[1] = lasthurt = 0; 321 resetresidualowner(); 322 clientstate::respawn(millis); 323 } 324 updateweaptimeserver::servstate325 void updateweaptime() 326 { 327 extern int gamemillis; 328 if(lasttimewielded && isalive(gamemillis)) 329 { 330 int millis = totalmillis-lasttimewielded, secs = millis/1000; 331 weapstats[weapselect].timewielded += secs; 332 lasttimewielded = totalmillis+(secs*1000)-millis; 333 loopi(W_MAX) 334 { 335 if(lasttimeloadout[i] && holdweap(i, m_weapon(actortype, gamemode, mutators), lastmillis)) 336 { 337 int millis = totalmillis-lasttimeloadout[i], secs = millis/1000; 338 weapstats[i].timeloadout += secs; 339 lasttimeloadout[i] = totalmillis+(secs*1000)-millis; 340 } 341 else lasttimeloadout[i] = totalmillis ? totalmillis : 1; 342 } 343 } 344 else 345 { 346 lasttimewielded = totalmillis ? totalmillis : 1; 347 loopi(W_MAX) lasttimeloadout[i] = totalmillis ? totalmillis : 1; 348 } 349 } 350 updatetimeplayedserver::servstate351 void updatetimeplayed() 352 { 353 clientstate::updatetimeplayed(); 354 extern int gamemillis; 355 if(lasttimealive && isalive(gamemillis)) 356 { 357 int millis = totalmillis-lasttimealive, secs = millis/1000; 358 timealive += secs; 359 lasttimealive = totalmillis+(secs*1000)-millis; 360 } 361 else lasttimealive = totalmillis ? totalmillis : 1; 362 if(lasttimeactive && (state == CS_ALIVE || state == CS_DEAD || state == CS_WAITING)) 363 { 364 int millis = totalmillis-lasttimeactive, secs = millis/1000; 365 timeactive += secs; 366 lasttimeactive = totalmillis+(secs*1000)-millis; 367 } 368 else lasttimeactive = totalmillis ? totalmillis : 1; 369 updateweaptime(); 370 } 371 feetposserver::servstate372 vec feetpos(float offset = 0) const { return vec(o).add(vec(0, 0, offset)); } headposserver::servstate373 vec headpos(float offset = 0) const { return vec(o).add(vec(0, 0, offset+actor[actortype].height)); } centerserver::servstate374 vec center() const { return vec(o).add(vec(0, 0, actor[actortype].height*0.5f)); } 375 }; 376 377 struct votecount 378 { 379 char *map; 380 int mode, muts, count; votecountserver::votecount381 votecount() {} votecountserver::votecount382 votecount(char *s, int n, int m) : map(s), mode(n), muts(m), count(0) {} 383 }; 384 385 struct clientinfo : servstate 386 { 387 string name, handle, mapvote, authname, clientmap; 388 int clientnum, connectmillis, sessionid, overflow, ping, team, lastteam, lastplayerinfo, 389 modevote, mutsvote, lastvote, privilege, oldprivilege, gameoffset, lastevent, wslen, swapteam, clientcrc; 390 bool connected, ready, local, timesync, online, wantsmap, gettingmap, connectauth, kicked; 391 vector<gameevent *> events; 392 vector<uchar> position, messages; 393 uchar *wsdata; 394 vector<clientinfo *> bots; 395 uint authreq; 396 ENetPacket *clipboard; 397 int lastclipboard, needclipboard; 398 clientinfoserver::clientinfo399 clientinfo() : clipboard(NULL) { reset(); } ~clientinfoserver::clientinfo400 ~clientinfo() { events.deletecontents(); cleanclipboard(); } 401 addeventserver::clientinfo402 void addevent(gameevent *e) 403 { 404 if(state==CS_SPECTATOR || events.length()>250) delete e; 405 else events.add(e); 406 } 407 mapchangeserver::clientinfo408 void mapchange(bool change = true) 409 { 410 mapvote[0] = '\0'; 411 modevote = mutsvote = -1; 412 servstate::mapchange(change); 413 events.deletecontents(); 414 overflow = 0; 415 ready = timesync = wantsmap = gettingmap = false; 416 lastevent = gameoffset = lastvote = clientcrc = 0; 417 if(!change) lastteam = T_NEUTRAL; 418 team = swapteam = T_NEUTRAL; 419 clientmap[0] = '\0'; 420 if(handle[0]) 421 { 422 requestmasterf("reqauthstats \"%s\"\n", handle); 423 flushmasteroutput(); 424 } 425 } 426 cleanclipboardserver::clientinfo427 void cleanclipboard(bool fullclean = true) 428 { 429 if(clipboard) { if(--clipboard->referenceCount <= 0) enet_packet_destroy(clipboard); clipboard = NULL; } 430 if(fullclean) lastclipboard = 0; 431 } 432 resetserver::clientinfo433 void reset() 434 { 435 ping = lastplayerinfo = 0; 436 name[0] = handle[0] = '\0'; 437 privilege = PRIV_NONE; 438 oldprivilege = -1; 439 connected = ready = local = online = wantsmap = gettingmap = connectauth = kicked = false; 440 authreq = 0; 441 position.setsize(0); 442 messages.setsize(0); 443 needclipboard = 0; 444 cleanclipboard(); 445 mapchange(false); 446 } 447 getmillisserver::clientinfo448 int getmillis(int millis, int id) 449 { 450 if(!timesync) 451 { 452 timesync = true; 453 gameoffset = millis-id; 454 return millis; 455 } 456 return gameoffset+id; 457 } 458 isreadyserver::clientinfo459 bool isready() 460 { 461 return ready && !wantsmap; 462 } 463 }; 464 465 struct savedscore 466 { 467 uint ip; 468 string name, handle; 469 int points, frags, deaths, localtotalpoints, localtotalfrags, localtotaldeaths, spree, rewards, timeplayed, timealive, timeactive, shotdamage, damage, cptime, actortype; 470 int warnings[WARN_MAX][2]; 471 bool quarantine; 472 weaponstats weapstats[W_MAX]; 473 vector<teamkill> teamkills; 474 vector<capturestats> captures; 475 vector<bombstats> bombings; 476 vector<ffaroundstats> ffarounds; 477 saveserver::savedscore478 void save(clientinfo *ci) 479 { 480 points = ci->points; 481 frags = ci->frags; 482 deaths = ci->deaths; 483 localtotalpoints = ci->localtotalpoints; 484 localtotalfrags = ci->localtotalfrags; 485 localtotaldeaths = ci->localtotaldeaths; 486 spree = ci->spree; 487 rewards = ci->rewards[0]; 488 timeplayed = ci->timeplayed; 489 timealive = ci->timealive; 490 timeactive = ci->timeactive; 491 shotdamage = ci->shotdamage; 492 damage = ci->damage; 493 cptime = ci->cptime; 494 actortype = ci->actortype; 495 loopi(W_MAX) weapstats[i] = ci->weapstats[i]; 496 loopi(WARN_MAX) loopj(2) warnings[i][j] = ci->warnings[i][j]; 497 quarantine = ci->quarantine; 498 teamkills.shrink(0); 499 loopv(ci->teamkills) teamkills.add(ci->teamkills[i]); 500 captures.shrink(0); 501 loopv(ci->captures) captures.add(ci->captures[i]); 502 bombings.shrink(0); 503 loopv(ci->bombings) bombings.add(ci->bombings[i]); 504 ffarounds.shrink(0); 505 loopv(ci->ffarounds) ffarounds.add(ci->ffarounds[i]); 506 } 507 restoreserver::savedscore508 void restore(clientinfo *ci) 509 { 510 ci->points = points; 511 ci->frags = frags; 512 ci->deaths = deaths; 513 ci->localtotalpoints = localtotalpoints; 514 ci->localtotalfrags = localtotalfrags; 515 ci->localtotaldeaths = localtotaldeaths; 516 ci->totalpoints = localtotalpoints; 517 ci->totalfrags = localtotalfrags; 518 ci->totaldeaths = localtotaldeaths; 519 ci->spree = spree; 520 ci->rewards[0] = rewards; 521 ci->timeplayed = timeplayed; 522 ci->timealive = timealive; 523 ci->timeactive = timeactive; 524 ci->shotdamage = shotdamage; 525 ci->damage = damage; 526 ci->cptime = cptime; 527 loopi(W_MAX) ci->weapstats[i] = weapstats[i]; 528 loopi(WARN_MAX) loopj(2) ci->warnings[i][j] = warnings[i][j]; 529 ci->quarantine = quarantine; 530 ci->teamkills.shrink(0); 531 loopv(teamkills) ci->teamkills.add(teamkills[i]); 532 ci->captures.shrink(0); 533 loopv(captures) ci->captures.add(captures[i]); 534 ci->bombings.shrink(0); 535 loopv(bombings) ci->bombings.add(bombings[i]); 536 ci->ffarounds.shrink(0); 537 loopv(ffarounds) ci->ffarounds.add(ffarounds[i]); 538 } 539 mapchangeserver::savedscore540 void mapchange() 541 { 542 points = frags = spree = rewards = deaths = timeplayed = timealive = timeactive = shotdamage = damage = cptime = 0; 543 actortype = A_MAX; 544 teamkills.shrink(0); 545 captures.shrink(0); 546 bombings.shrink(0); 547 ffarounds.shrink(0); 548 loopi(W_MAX) weapstats[i].reset(); 549 } 550 }; 551 552 namespace aiman { 553 extern void setskill(clientinfo *ci); 554 extern bool addai(int type, int ent = -1, int skill = -1); 555 extern void deleteai(clientinfo *ci); 556 extern bool delai(int type, bool skip = true); 557 extern void removeai(clientinfo *ci, bool complete = false); 558 extern bool reassignai(clientinfo *exclude = NULL); 559 extern void checkskills(); 560 extern void clearai(int type = 0); 561 extern void checkai(); 562 extern void poke(); 563 } 564 565 string smapname = ""; 566 int smapcrc = 0, mapsending = -1, mapgameinfo = -1, gamestate = G_S_WAITING, gamemode = G_EDITMODE, mutators = 0, gamemillis = 0, gamelimit = 0, 567 mastermode = MM_OPEN, timeremaining = -1, oldtimelimit = -1, gamewaittime = 0, lastteambalance = 0, nextteambalance = 0, lastrotatecycle = 0; 568 bool hasgameinfo = false, updatecontrols = false, shouldcheckvotes = false, firstblood = false, sentstats = false; 569 enet_uint32 lastsend = 0; 570 stream *mapdata[SENDMAP_MAX] = { NULL }; 571 vector<clientinfo *> clients, connects; 572 573 struct demofile 574 { 575 string info; 576 uchar *data; 577 int ctime, len; 578 }; 579 580 vector<demofile> demos; 581 582 bool demonextmatch = false; 583 stream *demotmp = NULL, *demorecord = NULL, *demoplayback = NULL; 584 int nextplayback = 0, triggerid = 0; 585 struct triggergrp 586 { 587 int id; 588 vector<int> ents; triggergrpserver::triggergrp589 triggergrp() { reset(); } resetserver::triggergrp590 void reset(int n = 0) { id = n; ents.shrink(0); } 591 } triggers[TRIGGERIDS+1]; 592 canplay()593 bool canplay() 594 { 595 if(!demoplayback && !m_demo(gamemode)) 596 if((m_play(gamemode) && !hasgameinfo) || !gs_playing(gamestate)) return false; 597 return true; 598 } 599 600 struct servmode 601 { servmodeserver::servmode602 servmode() {} ~servmodeserver::servmode603 virtual ~servmode() {} 604 entergameserver::servmode605 virtual void entergame(clientinfo *ci) {} leavegameserver::servmode606 virtual void leavegame(clientinfo *ci, bool disconnecting = false) {} 607 movedserver::servmode608 virtual void moved(clientinfo *ci, const vec &oldpos, const vec &newpos) {} canspawnserver::servmode609 virtual bool canspawn(clientinfo *ci, bool tryspawn = false) { return true; } spawnedserver::servmode610 virtual void spawned(clientinfo *ci) {} pointsserver::servmode611 virtual int points(clientinfo *m, clientinfo *v) 612 { 613 if(m==v || m->team == v->team) return -1; 614 return 1; 615 } diedserver::servmode616 virtual void died(clientinfo *m, clientinfo *v = NULL) {} changeteamserver::servmode617 virtual void changeteam(clientinfo *ci, int oldteam, int newteam) {} initclientserver::servmode618 virtual void initclient(clientinfo *ci, packetbuf &p, bool connecting) {} updateserver::servmode619 virtual void update() {} resetserver::servmode620 virtual void reset() {} layoutserver::servmode621 virtual void layout() {} balanceserver::servmode622 virtual void balance(int oldbalance) {} intermissionserver::servmode623 virtual void intermission() {} wantsovertimeserver::servmode624 virtual bool wantsovertime() { return false; } damageserver::servmode625 virtual bool damage(clientinfo *m, clientinfo *v, int damage, int weap, int flags, int material, const ivec &hitpush = ivec(0, 0, 0), const ivec &hitvel = ivec(0, 0, 0), float dist = 0) { return true; } dodamageserver::servmode626 virtual void dodamage(clientinfo *m, clientinfo *v, int &damage, int &hurt, int &weap, int &flags, int &material, const ivec &hitpush = ivec(0, 0, 0), const ivec &hitvel = ivec(0, 0, 0), float dist = 0) { } regenserver::servmode627 virtual void regen(clientinfo *ci, int &total, int &amt, int &delay) {} checkclientserver::servmode628 virtual void checkclient(clientinfo *ci) {} scoreaffinityserver::servmode629 virtual void scoreaffinity(clientinfo *ci, bool win = true) {} canbalanceserver::servmode630 virtual bool canbalance() { return true; } 631 }; 632 633 vector<srventity> sents; 634 vector<savedscore> savedscores; 635 vector<savedscore> savedstatsscores; 636 servmode *smode; 637 vector<servmode *> smuts; 638 #define mutate(a,b) { loopvk(a) { servmode *mut = a[k]; { b; } } } 639 int curbalance = 0, nextbalance = 0, totalspawns = 0; 640 bool teamspawns = false; 641 642 vector<score> scores; teamscore(int team)643 score &teamscore(int team) 644 { 645 loopv(scores) 646 { 647 score &cs = scores[i]; 648 if(cs.team == team) return cs; 649 } 650 score &cs = scores.add(); 651 cs.team = team; 652 cs.total = 0; 653 return cs; 654 } 655 chkloadweap(clientinfo * ci,bool request=true)656 bool chkloadweap(clientinfo *ci, bool request = true) 657 { 658 if(ci->actortype == A_PLAYER && ci->loadweap.empty()) 659 { 660 if(request) 661 { 662 ci->lastplayerinfo = 0; 663 sendf(ci->clientnum, 1, "ri", N_LOADW); 664 } 665 return false; 666 } 667 return true; 668 } 669 setspawn(int ent,bool spawned,bool clear=false,bool msg=false)670 void setspawn(int ent, bool spawned, bool clear = false, bool msg = false) 671 { 672 if(sents.inrange(ent)) 673 { 674 if(clear) loopvk(clients) clients[k]->dropped.removeall(ent); 675 sents[ent].spawned = spawned; 676 sents[ent].millis = sents[ent].last = gamemillis; 677 if(sents[ent].type == WEAPON && !(sents[ent].attrs[1]&W_F_FORCED)) 678 { 679 int attr = w_attr(gamemode, mutators, sents[ent].type, sents[ent].attrs[0], m_weapon(A_PLAYER, gamemode, mutators)); 680 if(isweap(attr)) sents[ent].millis += w_spawn(attr); 681 } 682 else sents[ent].millis += G(itemspawntime); 683 if(msg) sendf(-1, 1, "ri3", N_ITEMSPAWN, ent, sents[ent].spawned ? 1 : 0); 684 } 685 } 686 takeammo(clientinfo * ci,int weap,int amt=1)687 void takeammo(clientinfo *ci, int weap, int amt = 1) { ci->ammo[weap] = max(ci->ammo[weap]-amt, 0); } 688 689 struct droplist { int weap, ent, ammo; }; 690 enum 691 { 692 DROP_NONE = 0, DROP_WEAPONS = 1<<0, DROP_WCLR = 1<<1, DROP_KAMIKAZE = 1<<2, DROP_EXPLODE = 1<<3, 693 DROP_DEATH = DROP_WEAPONS|DROP_KAMIKAZE, DROP_EXPIRE = DROP_WEAPONS|DROP_EXPLODE, DROP_RESET = DROP_WEAPONS|DROP_WCLR 694 }; 695 dropweapon(clientinfo * ci,int flags,int weap,vector<droplist> & drop)696 void dropweapon(clientinfo *ci, int flags, int weap, vector<droplist> &drop) 697 { 698 if(isweap(weap) && weap != m_weapon(ci->actortype, gamemode, mutators) && ci->hasweap(weap, m_weapon(ci->actortype, gamemode, mutators)) && sents.inrange(ci->entid[weap])) 699 { 700 setspawn(ci->entid[weap], false); 701 droplist &d = drop.add(); 702 d.weap = weap; 703 d.ent = ci->entid[weap]; 704 d.ammo = ci->ammo[weap]; 705 ci->dropped.add(d.ent, d.ammo); 706 ci->entid[weap] = -1; 707 if(flags&DROP_WCLR) ci->ammo[weap] = -1; 708 } 709 } 710 dropitems(clientinfo * ci,int flags=DROP_RESET)711 bool dropitems(clientinfo *ci, int flags = DROP_RESET) 712 { 713 bool kamikaze = false; 714 vector<droplist> drop; 715 if(flags&DROP_EXPLODE || (flags&DROP_KAMIKAZE && G(kamikaze) && (G(kamikaze) > 2 || (ci->hasweap(W_GRENADE, m_weapon(ci->actortype, gamemode, mutators)) && (G(kamikaze) > 1 || ci->weapselect == W_GRENADE))))) 716 { 717 ci->weapshots[W_GRENADE][0].add(1); 718 droplist &d = drop.add(); 719 d.weap = W_GRENADE; 720 d.ent = d.ammo = -1; 721 if(!(flags&DROP_EXPLODE)) takeammo(ci, W_GRENADE, W2(W_GRENADE, ammosub, false)); 722 kamikaze = true; 723 } 724 if(flags&DROP_WEAPONS) loopi(W_ALL) dropweapon(ci, flags, i, drop); 725 if(!drop.empty()) 726 sendf(-1, 1, "ri3iv", N_DROP, ci->clientnum, -1, drop.length(), drop.length()*sizeof(droplist)/sizeof(int), drop.getbuf()); 727 return kamikaze; 728 } 729 730 struct vampireservmode : servmode 731 { vampireservmodeserver::vampireservmode732 vampireservmode() {} dodamageserver::vampireservmode733 void dodamage(clientinfo *m, clientinfo *v, int &damage, int &hurt, int &weap, int &flags, int &material, const ivec &hitpush, const ivec &hitvel, float dist) 734 { 735 if(v != m && (!m_team(gamemode, mutators) || v->team != m->team) && v->state == CS_ALIVE && hurt > 0) 736 { 737 int real = int(ceilf(hurt*G(vampirescale))), heal = v->health+real; 738 if(AA(v->actortype, abilities)&(1<<A_A_REGEN)) heal = min(heal, m_maxhealth(gamemode, mutators, v->actortype)); 739 int eff = heal-v->health; 740 if(eff > 0) 741 { 742 v->health = heal; 743 v->lastregen = gamemillis; 744 v->lastregenamt = eff; 745 sendf(-1, 1, "ri4", N_REGEN, v->clientnum, v->health, v->lastregenamt); 746 } 747 } 748 } 749 } vampiremutator; 750 751 extern bool canbalancenow(); 752 753 struct spawnservmode : servmode // pseudo-mutator to regulate spawning clients 754 { 755 vector<clientinfo *> spawnq, playing; 756 spawnservmodeserver::spawnservmode757 spawnservmode() {} 758 spawnqueueserver::spawnservmode759 bool spawnqueue(bool all = false, bool needinfo = true) 760 { 761 return m_play(gamemode) && !m_race(gamemode) && !m_duke(gamemode, mutators) && G(maxalive) > 0 && (!needinfo || canplay()) && (!all || G(maxalivequeue)) && numclients() > 1; 762 } 763 queueserver::spawnservmode764 void queue(clientinfo *ci, bool msg = true, bool wait = true, bool top = false) 765 { 766 if(spawnqueue(true) && ci->online && ci->actortype < A_ENEMY && ci->state != CS_SPECTATOR && ci->state != CS_EDITING) 767 { 768 int n = spawnq.find(ci); 769 playing.removeobj(ci); 770 if(top) 771 { 772 if(n >= 0) spawnq.remove(n); 773 spawnq.insert(0, ci); 774 } 775 else if(n < 0) spawnq.add(ci); 776 if(wait && ci->state != CS_WAITING) waiting(ci, DROP_RESET); 777 if(msg && allowbroadcast(ci->clientnum) && !top) 778 { 779 int x = max(int(G(maxalive)*G(maxplayers)), max(int(numclients()*G(maxalivethreshold)), G(maxaliveminimum))); 780 if(m_team(gamemode, mutators)) 781 { 782 if(x%2) x++; 783 x = x/2; 784 if(m_coop(gamemode, mutators) && ci->actortype == A_BOT) 785 x = int(x*G(coopbalance)); 786 } 787 int slots = x; 788 loopv(playing) if(playing[i] && ci->team == playing[i]->team) slots--; 789 if(!slots) 790 { 791 int qn = 0; 792 loopv(spawnq) if(spawnq[i] && spawnq[i]->team == ci->team && spawnq[i]->actortype == A_PLAYER) 793 { 794 qn++; 795 if(spawnq[i] == ci) 796 { 797 spawnq[i]->queuepos = qn; 798 sendf(-1, 1, "ri3", N_QUEUEPOS, spawnq[i]->clientnum, spawnq[i]->queuepos); 799 break; 800 } 801 } 802 } 803 } 804 } 805 } 806 entergameserver::spawnservmode807 void entergame(clientinfo *ci) 808 { 809 spawnq.removeobj(ci); 810 playing.removeobj(ci); 811 queue(ci); 812 } 813 leavegameserver::spawnservmode814 void leavegame(clientinfo *ci, bool disconnecting = false) 815 { 816 spawnq.removeobj(ci); 817 playing.removeobj(ci); 818 } 819 canspawnserver::spawnservmode820 bool canspawn(clientinfo *ci, bool tryspawn = false) 821 { 822 if(ci->actortype >= A_ENEMY || !m_play(gamemode)) return true; 823 else if(tryspawn) 824 { 825 if(m_loadout(gamemode, mutators) && !chkloadweap(ci)) return false; 826 if(spawnqueue(true) && spawnq.find(ci) < 0 && playing.find(ci) < 0) queue(ci); 827 return true; 828 } 829 if(m_balance(gamemode, mutators, teamspawns) && G(balancenospawn) && nextbalance && m_balreset(gamemode, mutators) && canbalancenow()) return false; 830 int delay = m_delay(ci->actortype, gamemode, mutators, ci->team); 831 if(delay && ci->respawnwait(gamemillis, delay)) return false; 832 if(spawnqueue() && playing.find(ci) < 0) 833 { 834 if(!canplay()) return false; 835 if(G(maxalivequeue) && spawnq.find(ci) < 0) queue(ci); 836 int x = max(int(G(maxalive)*G(maxplayers)), max(int(numclients()*G(maxalivethreshold)), G(maxaliveminimum))); 837 if(m_team(gamemode, mutators)) 838 { 839 if(x%2) x++; 840 x = x/2; 841 if(m_coop(gamemode, mutators) && ci->actortype == A_BOT) 842 x = int(x*G(coopbalance)); 843 } 844 int alive = 0; 845 loopv(playing) 846 { 847 if(playing[i]->state != CS_DEAD && playing[i]->state != CS_ALIVE) 848 { 849 if(playing[i]->state != CS_WAITING || !G(maxalivequeue)) 850 { 851 playing.removeobj(playing[i--]); 852 continue; 853 } 854 } 855 if(spawnq.find(playing[i]) >= 0) spawnq.removeobj(playing[i]); 856 if(ci->team == playing[i]->team) alive++; 857 } 858 if(alive >= x) 859 { 860 if(ci->actortype == A_PLAYER) loopv(playing) 861 { // kill off bots for the human 862 if(playing[i]->actortype != A_BOT || ci->team != playing[i]->team) 863 continue; 864 queue(playing[i--]); 865 if(--alive < x) break; 866 } 867 if(alive >= x) return false; 868 } 869 if(G(maxalivequeue)) 870 { 871 if(ci->actortype == A_BOT) loopv(spawnq) if(spawnq[i]->team == ci->team) 872 { 873 if(spawnq[i] != ci && spawnq[i]->actortype == A_PLAYER) return false; 874 break; 875 } 876 // at this point is where it decides this player is spawning, so tell everyone else their position 877 if(x-alive == 1) 878 { 879 int qn = 0; 880 loopv(spawnq) if(spawnq[i] != ci && spawnq[i]->team == ci->team && spawnq[i]->actortype == A_PLAYER) 881 { 882 qn++; 883 if(allowbroadcast(spawnq[i]->clientnum)) 884 { 885 spawnq[i]->queuepos = qn; 886 sendf(-1, 1, "ri3", N_QUEUEPOS, spawnq[i]->clientnum, spawnq[i]->queuepos); 887 } 888 } 889 } 890 } 891 spawnq.removeobj(ci); 892 if(playing.find(ci) < 0) playing.add(ci); 893 } 894 return true; 895 } 896 spawnedserver::spawnservmode897 void spawned(clientinfo *ci) 898 { 899 spawnq.removeobj(ci); 900 if(playing.find(ci) < 0) queue(ci); 901 } 902 diedserver::spawnservmode903 void died(clientinfo *ci, clientinfo *at) 904 { 905 spawnq.removeobj(ci); 906 if(G(maxalivequeue)) playing.removeobj(ci); 907 } 908 resetserver::spawnservmode909 void reset() 910 { 911 spawnq.shrink(0); 912 playing.shrink(0); 913 } 914 } spawnmutator; 915 canbalancenow()916 bool canbalancenow() 917 { 918 bool ret = true; 919 if(smode) if(!smode->canbalance()) ret = false; 920 if(ret) mutate(smuts, if(!mut->canbalance()) { ret = false; break; }); 921 return ret; 922 } 923 924 SVAR(0, serverpass, ""); 925 SVAR(0, adminpass, ""); 926 927 int sversion[2] = {0}; 928 ICOMMAND(0, setversion, "ii", (int *a, int *b), sversion[0] = *a; sversion[1] = *b); 929 mastermask()930 int mastermask() 931 { 932 switch(G(serveropen)) 933 { 934 case 0: default: return MM_FREESERV; break; 935 case 1: return MM_OPENSERV; break; 936 case 2: return MM_COOPSERV; break; 937 case 3: return MM_VETOSERV; break; 938 } 939 return 0; 940 } 941 942 #define setmod(a,b) \ 943 { \ 944 if(a != b) \ 945 { \ 946 ident *id = getident(#a); \ 947 if(id && id->type == ID_VAR && id->flags&IDF_SERVER) \ 948 { \ 949 *id->storage.i = clamp(b, id->minval, id->maxval); \ 950 id->changed(); \ 951 const char *sval = intstr(id); \ 952 sendf(-1, 1, "ri2sis", N_COMMAND, -1, &id->name[3], strlen(sval), sval); \ 953 } \ 954 } \ 955 } 956 #define setmodf(a,b) \ 957 { \ 958 if(a != b) \ 959 { \ 960 ident *id = getident(#a); \ 961 if(id && id->type == ID_FVAR && id->flags&IDF_SERVER) \ 962 { \ 963 *id->storage.f = clamp(b, id->minvalf, id->maxvalf); \ 964 id->changed(); \ 965 const char *sval = floatstr(id); \ 966 if(sval) sendf(-1, 1, "ri2sis", N_COMMAND, -1, &id->name[3], strlen(sval), sval); \ 967 } \ 968 } \ 969 } 970 #define setmods(a,b) \ 971 { \ 972 if(strcmp(a, b)) \ 973 { \ 974 ident *id = getident(#a); \ 975 if(id && id->type == ID_SVAR && id->flags&IDF_SERVER) \ 976 { \ 977 delete[] *id->storage.s; \ 978 *id->storage.s = newstring(b); \ 979 sendf(-1, 1, "ri2sis", N_COMMAND, -1, &id->name[3], strlen(*id->storage.s), *id->storage.s); \ 980 } \ 981 } \ 982 } 983 984 int numgamevars = 0, numgamemods = 0; resetgamevars(bool all)985 void resetgamevars(bool all) 986 { 987 numgamevars = numgamemods = 0; 988 enumerate(idents, ident, id, { 989 if(id.flags&IDF_SERVER && !(id.flags&IDF_READONLY) && (all || !(id.flags&IDF_WORLD))) // reset vars 990 { 991 const char *val = NULL; 992 if(id.flags&IDF_GAMEMOD) numgamevars++; 993 switch(id.type) 994 { 995 case ID_VAR: 996 { 997 if(*id.storage.i != id.def.i) 998 { 999 setvar(id.name, id.def.i, true); 1000 val = intstr(&id); 1001 } 1002 if(id.flags&IDF_GAMEMOD && *id.storage.i != id.bin.i) numgamemods++; 1003 break; 1004 } 1005 case ID_FVAR: 1006 { 1007 if(*id.storage.f != id.def.f) 1008 { 1009 setfvar(id.name, id.def.f, true); 1010 val = floatstr(*id.storage.f); 1011 } 1012 if(id.flags&IDF_GAMEMOD && *id.storage.f != id.bin.f) numgamemods++; 1013 break; 1014 } 1015 case ID_SVAR: 1016 { 1017 if(strcmp(*id.storage.s, id.bin.s)) 1018 { 1019 setsvar(id.name, id.def.s && *id.def.s ? id.def.s : "", true); 1020 val = *id.storage.s; 1021 } 1022 if(id.flags&IDF_GAMEMOD && strcmp(*id.storage.s, id.bin.s)) numgamemods++; 1023 break; 1024 } 1025 default: break; 1026 } 1027 if(val) sendf(-1, 1, "ri2sis", N_COMMAND, -1, &id.name[3], strlen(val), val); 1028 } 1029 }); 1030 } 1031 savegamevars()1032 void savegamevars() 1033 { 1034 enumerate(idents, ident, id, { 1035 if(id.flags&IDF_SERVER && !(id.flags&IDF_READONLY) && !(id.flags&IDF_WORLD)) switch(id.type) 1036 { 1037 case ID_VAR: id.def.i = *id.storage.i; break; 1038 case ID_FVAR: id.def.f = *id.storage.f; break; 1039 case ID_SVAR: 1040 { 1041 delete[] id.def.s; 1042 id.def.s = newstring(*id.storage.s); 1043 break; 1044 } 1045 default: break; 1046 } 1047 }); 1048 } 1049 pickmap(const char * suggest,int mode,int muts,bool notry)1050 const char *pickmap(const char *suggest, int mode, int muts, bool notry) 1051 { 1052 const char *map = G(defaultmap); 1053 if(!notry) 1054 { 1055 if(!map || !*map) map = choosemap(suggest, mode, muts, G(rotatemaps), true); 1056 else if(strchr(map, ' ')) 1057 { 1058 static string defaultmap; 1059 defaultmap[0] = '\0'; 1060 vector<char *> maps; 1061 explodelist(map, maps); 1062 if(*sv_previousmaps) 1063 { 1064 vector<char *> prev; 1065 explodelist(sv_previousmaps, prev); 1066 loopvj(prev) loopvrev(maps) if(strcmp(prev[j], maps[i])) 1067 { 1068 delete[] maps[i]; 1069 maps.remove(i); 1070 if(maps.length() <= 1) break; 1071 } 1072 prev.deletearrays(); 1073 } 1074 if(!maps.empty()) 1075 { 1076 int r = rnd(maps.length()); 1077 copystring(defaultmap, maps[r]); 1078 } 1079 maps.deletearrays(); 1080 map = *defaultmap ? defaultmap : choosemap(suggest, mode, muts, G(rotatemaps), true); 1081 } 1082 } 1083 return map && *map ? map : "maps/untitled"; 1084 } 1085 setpause(bool on=false)1086 void setpause(bool on = false) 1087 { 1088 if(on) { setmod(sv_gamepaused, 1); } 1089 else { setmod(sv_gamepaused, 0); } 1090 } 1091 setdemorecord(bool value,bool msg=false)1092 void setdemorecord(bool value, bool msg = false) 1093 { 1094 demonextmatch = value; 1095 if(msg) srvoutf(-3, "\fydemo recording is \fs\fc%s\fS for next match", demonextmatch ? "enabled" : "disabled"); 1096 } 1097 1098 void enddemorecord(bool full); checkdemorecord(bool full)1099 void checkdemorecord(bool full) 1100 { 1101 if(demorecord) enddemorecord(full); 1102 if(G(demoautorec) && !demonextmatch) setdemorecord(true); 1103 } 1104 resetbans()1105 void resetbans() 1106 { 1107 loopvrev(control) if(control[i].type == ipinfo::BAN && control[i].flag <= ipinfo::INTERNAL) control.remove(i); 1108 } 1109 resetallows()1110 void resetallows() 1111 { 1112 loopvrev(control) if(control[i].type == ipinfo::ALLOW && control[i].flag <= ipinfo::INTERNAL) control.remove(i); 1113 } 1114 resetmutes()1115 void resetmutes() 1116 { 1117 loopvrev(control) if(control[i].type == ipinfo::MUTE && control[i].flag <= ipinfo::INTERNAL) control.remove(i); 1118 } 1119 resetlimits()1120 void resetlimits() 1121 { 1122 loopvrev(control) if(control[i].type == ipinfo::LIMIT && control[i].flag <= ipinfo::INTERNAL) control.remove(i); 1123 } 1124 resetexcepts()1125 void resetexcepts() 1126 { 1127 loopvrev(control) if(control[i].type == ipinfo::EXCEPT && control[i].flag <= ipinfo::INTERNAL) control.remove(i); 1128 } 1129 cleanup(bool init=false)1130 void cleanup(bool init = false) 1131 { 1132 setpause(false); 1133 setmod(sv_botoffset, 0); 1134 if(G(resetmmonend)) { mastermode = MM_OPEN; resetallows(); } 1135 if(G(resetbansonend)) resetbans(); 1136 if(G(resetmutesonend)) resetmutes(); 1137 if(G(resetlimitsonend)) resetlimits(); 1138 if(G(resetexceptsonend)) resetexcepts(); 1139 if(G(resetvarsonend) || init) resetgamevars(true); 1140 changemap(); 1141 lastrotatecycle = clocktime; 1142 } 1143 start()1144 void start() 1145 { 1146 cleanup(true); 1147 } 1148 reload()1149 void reload() 1150 { 1151 extern void localopreset(); 1152 localopreset(); 1153 } 1154 shutdown()1155 void shutdown() 1156 { 1157 srvoutf(-3, "\fyserver shutdown in progress.."); 1158 aiman::clearai(); 1159 loopv(clients) if(getinfo(i)) disconnect_client(i, DISC_SHUTDOWN); 1160 } 1161 newinfo()1162 void *newinfo() { return new clientinfo; } deleteinfo(void * ci)1163 void deleteinfo(void *ci) { delete (clientinfo *)ci; } 1164 numchannels()1165 int numchannels() { return 3; } spectatorslots()1166 int spectatorslots() { return clamp(G(serverspectators) > 0 ? G(serverspectators) : G(serverclients), 1, MAXCLIENTS); } maxslots()1167 int maxslots() { return clamp(G(serverclients)+spectatorslots(), 1, MAXCLIENTS); } reserveclients()1168 int reserveclients() { return maxslots()+4; } dupclients()1169 int dupclients() { return G(serverdupclients); } 1170 hasclient(clientinfo * ci,clientinfo * cp=NULL)1171 bool hasclient(clientinfo *ci, clientinfo *cp = NULL) 1172 { 1173 if(!ci || (ci != cp && ci->clientnum != cp->clientnum && ci->ownernum != cp->clientnum)) return false; 1174 return true; 1175 } 1176 peerowner(int n)1177 int peerowner(int n) 1178 { 1179 clientinfo *ci = (clientinfo *)getinfo(n); 1180 if(ci && ci->actortype > A_PLAYER) return ci->ownernum; 1181 return n; 1182 } 1183 allowbroadcast(int n)1184 bool allowbroadcast(int n) 1185 { 1186 clientinfo *ci = (clientinfo *)getinfo(n); 1187 return ci && ci->connected && ci->actortype == A_PLAYER; 1188 } 1189 numclients(int exclude,bool nospec,int actortype)1190 int numclients(int exclude, bool nospec, int actortype) 1191 { 1192 int n = 0; 1193 loopv(clients) 1194 { 1195 if(clients[i]->clientnum >= 0 && clients[i]->name[0] && clients[i]->clientnum != exclude && 1196 (!nospec || clients[i]->state != CS_SPECTATOR) && 1197 (clients[i]->actortype == A_PLAYER || (actortype > A_PLAYER && clients[i]->actortype <= actortype && clients[i]->ownernum >= 0))) 1198 n++; 1199 } 1200 return n; 1201 } 1202 numspectators(int exclude=-1)1203 int numspectators(int exclude = -1) 1204 { 1205 int n = 0; 1206 loopv(clients) 1207 if(clients[i]->clientnum >= 0 && clients[i]->name[0] && clients[i]->clientnum != exclude && clients[i]->actortype == A_PLAYER && clients[i]->state == CS_SPECTATOR) 1208 n++; 1209 return n; 1210 } 1211 duplicatename(clientinfo * ci,char * name)1212 bool duplicatename(clientinfo *ci, char *name) 1213 { 1214 if(!name) name = ci->name; 1215 loopv(clients) if(clients[i]!=ci && !strcmp(name, clients[i]->name)) return true; 1216 return false; 1217 } 1218 findcolour(clientinfo * ci,bool tone=true)1219 int findcolour(clientinfo *ci, bool tone = true) 1220 { 1221 if(tone) 1222 { 1223 int col = ci->actortype < A_ENEMY ? ci->colour : 0x060606; 1224 if(!col && isweap(ci->weapselect)) col = W(ci->weapselect, colour); 1225 if(col) return col; 1226 } 1227 return TEAM(ci->team, colour); 1228 } 1229 privname(int priv,int actortype)1230 const char *privname(int priv, int actortype) 1231 { 1232 if(actortype != A_PLAYER) return "bot"; 1233 const char *privnames[2][PRIV_MAX] = { 1234 { "none", "player account", "global supporter", "global moderator", "global operator", "global administrator", "project developer", "project founder" }, 1235 { "none", "player account", "local supporter", "local moderator", "local operator", "local administrator", "none", "none" } 1236 }; 1237 return privnames[priv&PRIV_LOCAL ? 1 : 0][clamp(priv&PRIV_TYPE, 0, int(priv&PRIV_LOCAL ? PRIV_ADMINISTRATOR : PRIV_LAST))]; 1238 } 1239 privnamex(int priv,int actortype,bool local)1240 const char *privnamex(int priv, int actortype, bool local) 1241 { 1242 if(actortype != A_PLAYER) return "bot"; 1243 const char *privnames[2][PRIV_MAX] = { 1244 { "none", "player", "supporter", "moderator", "operator", "administrator", "developer", "founder" }, 1245 { "none", "player", "localsupporter", "localmoderator", "localoperator", "localadministrator", "developer", "founder" } 1246 }; 1247 return privnames[local && priv&PRIV_LOCAL ? 1 : 0][clamp(priv&PRIV_TYPE, 0, int(priv&PRIV_LOCAL ? PRIV_ADMINISTRATOR : PRIV_LAST))]; 1248 } 1249 colourname(clientinfo * ci,char * name=NULL,bool icon=true,bool dupname=true,int colour=3)1250 const char *colourname(clientinfo *ci, char *name = NULL, bool icon = true, bool dupname = true, int colour = 3) 1251 { 1252 if(!name) name = ci->name; 1253 static string colored; colored[0] = '\0'; string colortmp; 1254 if(colour) concatstring(colored, "\fs"); 1255 if(icon) 1256 { 1257 if(colour&1) 1258 { 1259 formatstring(colortmp, "\f[%d]", findcolour(ci)); 1260 concatstring(colored, colortmp); 1261 } 1262 formatstring(colortmp, "\f($priv%stex)", privnamex(ci->privilege, ci->actortype, true)); 1263 concatstring(colored, colortmp); 1264 } 1265 if(colour&2) 1266 { 1267 formatstring(colortmp, "\f[%d]", TEAM(ci->team, colour)); 1268 concatstring(colored, colortmp); 1269 } 1270 concatstring(colored, name); 1271 if(!name[0] || (ci->actortype < A_ENEMY && dupname && duplicatename(ci, name))) 1272 { 1273 formatstring(colortmp, "%s[%d]", name[0] ? " " : "", ci->clientnum); 1274 concatstring(colored, colortmp); 1275 } 1276 if(colour) concatstring(colored, "\fS"); 1277 return colored; 1278 } 1279 teamtexnamex(int team)1280 const char *teamtexnamex(int team) 1281 { 1282 const char *teamtexs[T_MAX] = { "teamtex", "teamalphatex", "teamomegatex", "teamkappatex", "teamsigmatex", "teamtex" }; 1283 return teamtexs[clamp(team, 0, T_MAX-1)]; 1284 } 1285 colourteam(int team,const char * icon="")1286 const char *colourteam(int team, const char *icon = "") 1287 { 1288 if(team < 0 || team > T_MAX) team = T_NEUTRAL; 1289 static string teamed; teamed[0] = '\0'; string teamtmp; 1290 concatstring(teamed, "\fs"); 1291 formatstring(teamtmp, "\f[%d]", TEAM(team, colour)); 1292 concatstring(teamed, teamtmp); 1293 if(icon != NULL) 1294 { 1295 formatstring(teamtmp, "\f($%s)", *icon ? icon : teamtexnamex(team)); 1296 concatstring(teamed, teamtmp); 1297 } 1298 concatstring(teamed, TEAM(team, name)); 1299 concatstring(teamed, "\fS"); 1300 return teamed; 1301 } 1302 haspriv(clientinfo * ci,int flag,const char * msg=NULL)1303 bool haspriv(clientinfo *ci, int flag, const char *msg = NULL) 1304 { 1305 if((ci->local && flag <= PRIV_MAX) || (ci->privilege&PRIV_TYPE) >= flag) return true; 1306 else if(mastermask()&MM_AUTOAPPROVE && flag <= PRIV_ELEVATED && !numclients(ci->clientnum)) return true; 1307 else if(msg && *msg) 1308 srvmsgft(ci->clientnum, CON_CHAT, "\fraccess denied, you need to be \fs\fc%s\fS to \fs\fc%s\fS", privnamex(flag), msg); 1309 return false; 1310 } 1311 cmppriv(clientinfo * ci,clientinfo * cp,const char * msg=NULL)1312 bool cmppriv(clientinfo *ci, clientinfo *cp, const char *msg = NULL) 1313 { 1314 string str = ""; 1315 if(msg && *msg) formatstring(str, "%s %s", msg, colourname(cp)); 1316 if(haspriv(ci, cp->local ? PRIV_ADMINISTRATOR : cp->privilege&PRIV_TYPE, str)) return true; 1317 return false; 1318 } 1319 gameid()1320 const char *gameid() { return VERSION_GAMEID; } 1321 ICOMMAND(0, gameid, "", (), result(gameid())); 1322 getver(int n)1323 int getver(int n) 1324 { 1325 switch(n) 1326 { 1327 case 0: return CUR_VERSION; 1328 case 1: return VERSION_GAME; 1329 case 2: case 3: return sversion[n%2]; 1330 case 4: return CUR_ARCH; 1331 default: break; 1332 } 1333 return 0; 1334 } 1335 ICOMMAND(0, getversion, "i", (int *a), intret(getver(*a))); 1336 gamename(int mode,int muts,int compact,int limit,const char * separator)1337 const char *gamename(int mode, int muts, int compact, int limit, const char *separator) 1338 { 1339 if(!m_game(mode)) mode = G_DEATHMATCH; 1340 if(gametype[mode].implied) muts |= gametype[mode].implied; 1341 static string gname; gname[0] = '\0'; 1342 int start = clamp(compact, 0, 3), lps = clamp(4-start, 1, 4); 1343 loopk(lps) 1344 { 1345 int iter = start+k; 1346 if(muts) 1347 { 1348 int implied = gametype[mode].implied; 1349 loopi(G_M_NUM) if(muts&(1<<mutstype[i].type)) implied |= mutstype[i].implied&~(1<<mutstype[i].type); 1350 loopi(G_M_NUM) if(muts&(1<<mutstype[i].type) && (!implied || !(implied&(1<<mutstype[i].type)))) 1351 { 1352 const char *mut = i < G_M_GSP ? mutstype[i].name : gametype[mode].gsp[i-G_M_GSP]; 1353 if(mut && *mut) 1354 { 1355 string name; 1356 switch(iter) 1357 { 1358 case 2: case 3: formatstring(name, "%s%s%c", *gname ? gname : "", *gname ? "-" : "", mut[0]); break; 1359 case 1: formatstring(name, "%s%s%c%c", *gname ? gname : "", *gname ? "-" : "", mut[0], mut[1]); break; 1360 case 0: default: formatstring(name, "%s%s%s", *gname ? gname : "", *gname ? "-" : "", mut); break; 1361 } 1362 copystring(gname, name); 1363 } 1364 } 1365 } 1366 defformatstring(mname, "%s%s%s", *gname ? gname : "", *gname ? separator : "", k < 3 ? gametype[mode].name : gametype[mode].sname); 1367 if(k < 3 && limit > 0 && int(strlen(mname)) >= limit) 1368 { 1369 gname[0] = '\0'; 1370 continue; // let's try again 1371 } 1372 copystring(gname, mname); 1373 break; 1374 } 1375 return gname; 1376 } 1377 ICOMMAND(0, gamename, "iiii", (int *g, int *m, int *c, int *t), result(gamename(*g, *m, *c, *t))); 1378 modedesc(int mode,int muts,int type)1379 const char *modedesc(int mode, int muts, int type) 1380 { 1381 if(!m_game(mode)) mode = G_DEATHMATCH; 1382 if(gametype[mode].implied) muts |= gametype[mode].implied; 1383 static string mdname; mdname[0] = '\0'; 1384 if(type == 1 || type == 3 || type == 4) concatstring(mdname, gametype[mode].name); 1385 if(type == 3 || type == 4) concatstring(mdname, ": "); 1386 if(type == 2 || type == 3 || type == 4 || type == 5) 1387 { 1388 if((type == 4 || type == 5) && m_ctf_protect(mode, muts)) concatstring(mdname, gametype[mode].gsd[2]); 1389 else if((type == 4 || type == 5) && m_dac_king(mode, muts)) concatstring(mdname, gametype[mode].gsd[1]); 1390 else if((type == 4 || type == 5) && m_bb_hold(mode, muts)) concatstring(mdname, gametype[mode].gsd[0]); 1391 else if((type == 4 || type == 5) && m_bb_attack(mode, muts)) concatstring(mdname, gametype[mode].gsd[2]); 1392 else if((type == 4 || type == 5) && m_ra_timed(mode, muts)) concatstring(mdname, gametype[mode].gsd[0]); 1393 else if((type == 4 || type == 5) && m_ra_gauntlet(mode, muts)) concatstring(mdname, gametype[mode].gsd[2]); 1394 else concatstring(mdname, gametype[mode].desc); 1395 } 1396 return mdname; 1397 } 1398 ICOMMAND(0, modedesc, "iii", (int *g, int *m, int *c), result(modedesc(*g, *m, *c))); 1399 mutsdesc(int mode,int muts,int type)1400 const char *mutsdesc(int mode, int muts, int type) 1401 { 1402 if(!m_game(mode)) mode = G_DEATHMATCH; 1403 static string mtname; mtname[0] = '\0'; 1404 int mutid = -1; 1405 loopi(G_M_NUM) if(muts == (1<<mutstype[i].type)) mutid = i; 1406 if(mutid < 0) return ""; 1407 if(type == 4 || type == 5) 1408 { 1409 if(m_ctf_protect(mode, muts)) return ""; 1410 else if(m_dac_king(mode, muts)) return ""; 1411 else if(m_bb_hold(mode, muts) || m_bb_attack(mode, muts)) return ""; 1412 else if(m_ra_timed(mode, muts) || m_ra_gauntlet(mode, muts)) return ""; 1413 } 1414 if(type == 1 || type == 3 || type == 4) 1415 { 1416 const char *n = mutid >= G_M_GSP ? gametype[mode].gsp[mutid-G_M_GSP] : mutstype[mutid].name; 1417 if(!n || !*n) return ""; 1418 concatstring(mtname, n); 1419 } 1420 if(type == 3 || type == 4) concatstring(mtname, ": "); 1421 if(type == 2 || type == 3 || type == 4 || type == 5) 1422 { 1423 const char *n = mutid >= G_M_GSP ? gametype[mode].gsd[mutid-G_M_GSP] : mutstype[mutid].desc; 1424 if(!n || !*n) return ""; 1425 concatstring(mtname, n); 1426 } 1427 return mtname; 1428 } 1429 ICOMMAND(0, mutsdesc, "iii", (int *g, int *m, int *c), result(mutsdesc(*g, *m, *c))); 1430 changemode(int & mode,int & muts)1431 void changemode(int &mode, int &muts) 1432 { 1433 if(mode < 0) 1434 { 1435 mode = G(defaultmode); 1436 if(G(rotatemode)) 1437 { 1438 int num = 0; 1439 loopi(G_MAX) if(G(rotatemodefilter)&(1<<i)) num++; 1440 if(!num) mode = rnd(G_RAND)+G_PLAY; 1441 else 1442 { 1443 int r = rnd(num), n = 0; 1444 loopi(G_MAX) if(G(rotatemodefilter)&(1<<i)) 1445 { 1446 if(n != r) n++; 1447 else { mode = i; break; } 1448 } 1449 } 1450 if(!mode || !(G(rotatemodefilter)&(1<<mode))) mode = rnd(G_RAND)+G_PLAY; 1451 } 1452 } 1453 if(muts < 0) 1454 { 1455 muts = G(defaultmuts); 1456 if(G(rotatemuts)) 1457 { 1458 int num = rnd(G_M_NUM+1); 1459 if(num) loopi(num) if(G(rotatemuts) == 1 || !rnd(G(rotatemuts))) 1460 { 1461 int rmut = 1<<rnd(G_M_NUM); 1462 if(G(rotatemutsfilter) && !(G(rotatemutsfilter)&rmut)) continue; 1463 muts |= rmut; 1464 modecheck(mode, muts, rmut); 1465 } 1466 } 1467 } 1468 modecheck(mode, muts); 1469 } 1470 choosemap(const char * suggest,int mode,int muts,int force,bool notry)1471 const char *choosemap(const char *suggest, int mode, int muts, int force, bool notry) 1472 { 1473 static string chosen; 1474 if(suggest && *suggest) 1475 { 1476 if(!strncasecmp(suggest, "maps/", 5) || !strncasecmp(suggest, "maps\\", 5)) 1477 copystring(chosen, suggest+5); 1478 else copystring(chosen, suggest); 1479 } 1480 else *chosen = 0; 1481 int rotate = force ? force : G(rotatemaps); 1482 if(rotate) loopj(2) 1483 { 1484 char *list = NULL; 1485 maplist(list, mode, muts, numclients(), G(rotatemapsfilter), j!=0); 1486 if(list) 1487 { 1488 bool found = false; 1489 int n = listlen(list), c = n ? rnd(n) : 0; 1490 if(c >= 0) 1491 { 1492 int len = 0; 1493 const char *elem = indexlist(list, c, len); 1494 if(len > 0) 1495 { 1496 copystring(chosen, elem, len+1); 1497 found = true; 1498 } 1499 } 1500 DELETEA(list); 1501 if(found) break; 1502 } 1503 } 1504 return *chosen ? chosen : pickmap(suggest, mode, muts, notry); 1505 } 1506 canload(const char * type)1507 bool canload(const char *type) 1508 { 1509 if(!strcmp(type, gameid())) return true; 1510 if(!strcmp(type, "bfa")) return true; 1511 if(!strcmp(type, "bfg")) return true; 1512 return false; 1513 } 1514 timeleft()1515 int timeleft() 1516 { 1517 switch(gamestate) 1518 { 1519 case G_S_PLAYING: case G_S_OVERTIME: return timeremaining; 1520 default: return gamewaittime ? max(gamewaittime-totalmillis, 0)/1000 : 0; 1521 } 1522 return 0; 1523 } 1524 sendtick()1525 void sendtick() 1526 { 1527 sendf(-1, 1, "ri3", N_TICK, gamestate, timeleft()); 1528 } 1529 1530 bool checkvotes(bool force = false); 1531 void sendstats(bool fromintermission = false); startintermission(bool req=false)1532 void startintermission(bool req = false) 1533 { 1534 if(gs_playing(gamestate)) 1535 { 1536 sendstats(true); 1537 setpause(false); 1538 timeremaining = 0; 1539 gamelimit = min(gamelimit, gamemillis); 1540 if(smode) smode->intermission(); 1541 mutate(smuts, mut->intermission()); 1542 } 1543 if(req || !G(intermlimit)) 1544 { 1545 checkdemorecord(true); 1546 if(gamestate != G_S_VOTING && G(votelimit)) 1547 { 1548 gamestate = G_S_VOTING; 1549 gamewaittime = totalmillis+G(votelimit); 1550 sendtick(); 1551 } 1552 else checkvotes(true); 1553 } 1554 else 1555 { 1556 gamestate = G_S_INTERMISSION; 1557 gamewaittime = totalmillis+G(intermlimit); 1558 sendtick(); 1559 } 1560 } 1561 wantsovertime()1562 bool wantsovertime() 1563 { 1564 if(smode && smode->wantsovertime()) return true; 1565 mutate(smuts, if(mut->wantsovertime()) return true); 1566 if(!m_mmvar(gamemode, mutators, overtimeallow) || m_balance(gamemode, mutators, teamspawns)) return false; 1567 bool result = false; 1568 if(m_team(gamemode, mutators)) 1569 { 1570 int best = -1; 1571 loopi(numteams(gamemode, mutators)) 1572 { 1573 score &cs = teamscore(i+T_FIRST); 1574 if(best < 0 || cs.total > teamscore(best).total) 1575 { 1576 best = i+T_FIRST; 1577 result = false; 1578 } 1579 else if(cs.total == teamscore(best).total) result = true; 1580 } 1581 } 1582 else 1583 { 1584 int best = -1; 1585 loopv(clients) if(clients[i]->actortype < A_ENEMY && clients[i]->state != CS_SPECTATOR) 1586 { 1587 if(best < 0 || (m_laptime(gamemode, mutators) ? (clients[best]->cptime <= 0 || (clients[i]->cptime > 0 && clients[i]->cptime < clients[best]->cptime)) : clients[i]->points > clients[best]->points)) 1588 { 1589 best = i; 1590 result = false; 1591 } 1592 else if(m_laptime(gamemode, mutators) ? clients[i]->cptime == clients[best]->cptime : clients[i]->points == clients[best]->points) result = true; 1593 } 1594 } 1595 return result; 1596 } 1597 balancecmp(clientinfo * a,clientinfo * b)1598 bool balancecmp(clientinfo *a, clientinfo *b) 1599 { 1600 return (a->balancescore() > b->balancescore()); 1601 } 1602 doteambalance(bool init)1603 void doteambalance(bool init) 1604 { 1605 vector<clientinfo *> tc[T_TOTAL]; 1606 int numplaying = 0; 1607 loopv(clients) 1608 { 1609 clientinfo *cp = clients[i]; 1610 if(!cp->team || cp->state == CS_SPECTATOR || cp->actortype > A_PLAYER) continue; 1611 cp->updatetimeplayed(); 1612 tc[cp->team-T_FIRST].add(cp); 1613 numplaying++; 1614 } 1615 if((G(teambalancestyle) || m_swapteam(gamemode, mutators)) && numplaying >= G(teambalanceplaying)) 1616 { 1617 int nt = numteams(gamemode, mutators), mid = numplaying/nt, pmax = -1, pmin = -1; 1618 loopi(nt) 1619 { 1620 int cl = tc[i].length(); 1621 if(pmax < 0 || cl > pmax) pmax = cl; 1622 if(pmin < 0 || cl < pmin) pmin = cl; 1623 } 1624 int offset = pmax-pmin; 1625 if(offset >= G(teambalanceamt)) 1626 { 1627 if(!init && !nextteambalance) 1628 { 1629 int secs = G(teambalancedelay)/1000; 1630 nextteambalance = gamemillis+G(teambalancedelay); 1631 ancmsgft(-1, S_V_BALWARN, CON_EVENT, "\fy\fs\fzoyWARNING:\fS \fs\fcteams\fS will be \fs\fcbalanced\fS in \fs\fc%d\fS %s", secs, secs != 1 ? "seconds" : "second"); 1632 } 1633 else if(init) 1634 { 1635 vector <clientinfo *> pool; 1636 loopvj(clients) 1637 { 1638 clientinfo *cp = clients[j]; 1639 if(!cp->team || cp->state == CS_SPECTATOR || cp->actortype > A_PLAYER) continue; 1640 pool.add(cp); 1641 setteam(cp, T_NEUTRAL, 0, false); 1642 } 1643 pool.sort(balancecmp); 1644 loopvj(pool) 1645 { 1646 clientinfo *cp = pool[j]; 1647 cp->swapteam = T_NEUTRAL; 1648 int t = chooseteam(cp, -1, true); 1649 if(t != cp->team) 1650 { 1651 setteam(cp, t, (m_balreset(gamemode, mutators) ? TT_RESET : 0)|TT_INFOSM, false); 1652 cp->lastdeath = 0; 1653 } 1654 } 1655 } 1656 else if(canbalancenow()) 1657 { 1658 int moved = 0; 1659 loopi(nt) for(int team = i+T_FIRST, iters = tc[i].length(); iters > 0 && tc[i].length() > mid; iters--) 1660 { 1661 int id = -1; 1662 loopvj(tc[i]) 1663 { 1664 clientinfo *cp = tc[i][j]; 1665 if(m_swapteam(gamemode, mutators) && cp->swapteam && cp->swapteam == team) { id = j; break; } 1666 if(G(teambalancestyle) == 0) 1667 { 1668 if(id < 0) id = j; 1669 } 1670 else if(G(teambalancehighest)) 1671 { 1672 if(id < 0 || tc[i][id]->balancescore() < cp->balancescore()) id = j; 1673 } 1674 else 1675 { 1676 if(id < 0 || tc[i][id]->balancescore() > cp->balancescore()) id = j; 1677 } 1678 } 1679 if(id >= 0) 1680 { 1681 clientinfo *cp = tc[i][id]; 1682 cp->swapteam = T_NEUTRAL; // make them rechoose if necessary 1683 int t = chooseteam(cp, -1, true); 1684 if(t != cp->team) 1685 { 1686 setteam(cp, t, (m_balreset(gamemode, mutators) ? TT_RESET : 0)|TT_INFOSM, false); 1687 cp->lastdeath = 0; 1688 tc[i].removeobj(cp); 1689 tc[t-T_FIRST].add(cp); 1690 moved++; 1691 } 1692 } 1693 else break; // won't get any more 1694 } 1695 if(!init) 1696 { 1697 if(moved) ancmsgft(-1, S_V_BALALERT, CON_EVENT, "\fy\fs\fzoyALERT:\fS \fs\fcteams\fS have now been \fs\fcbalanced\fS"); 1698 else ancmsgft(-1, S_V_NOTIFY, CON_EVENT, "\fy\fs\fzoyALERT:\fS \fs\fcteams\fS failed to be \fs\fcbalanced\fS"); 1699 } 1700 lastteambalance = gamemillis+G(teambalancewait); 1701 nextteambalance = 0; 1702 } 1703 } 1704 else 1705 { 1706 if(!init && nextteambalance) ancmsgft(-1, S_V_NOTIFY, CON_EVENT, "\fy\fs\fzoyALERT:\fS \fs\fcteams\fS no longer need to be \fs\fcbalanced\fS"); 1707 lastteambalance = gamemillis+(nextteambalance ? G(teambalancewait) : G(teambalancedelay)); 1708 nextteambalance = 0; 1709 } 1710 } 1711 else 1712 { 1713 if(!init && nextteambalance) ancmsgft(-1, S_V_NOTIFY, CON_EVENT, "\fy\fs\fzoyALERT:\fS \fs\fcteams\fS are no longer able to be \fs\fcbalanced\fS"); 1714 lastteambalance = gamemillis+(nextteambalance ? G(teambalancewait) : G(teambalancedelay)); 1715 nextteambalance = 0; 1716 } 1717 } 1718 checklimits()1719 void checklimits() 1720 { 1721 if(!m_play(gamemode)) return; 1722 bool wasinovertime = gamestate == G_S_OVERTIME; 1723 int limit = wasinovertime ? m_mmvar(gamemode, mutators, overtimelimit) : m_mmvar(gamemode, mutators, timelimit), numt = numteams(gamemode, mutators); 1724 bool newlimit = limit != oldtimelimit, newtimer = gamemillis-curtime>0 && gamemillis/1000!=(gamemillis-curtime)/1000, 1725 iterate = newlimit || newtimer; 1726 if(iterate) 1727 { 1728 if(newlimit) 1729 { 1730 if(limit && oldtimelimit) gamelimit += (limit-oldtimelimit)*60000; 1731 else if(limit) gamelimit = max(gamemillis, limit*60000); 1732 oldtimelimit = limit; 1733 } 1734 if(timeremaining) 1735 { 1736 if(limit) 1737 { 1738 if(gamemillis >= gamelimit) timeremaining = 0; 1739 else timeremaining = (gamelimit-gamemillis+999)/1000; 1740 } 1741 else timeremaining = -1; 1742 bool wantsoneminute = true; 1743 if(!timeremaining) 1744 { 1745 if(gamestate != G_S_OVERTIME && wantsovertime()) 1746 { 1747 limit = oldtimelimit = m_mmvar(gamemode, mutators, overtimelimit); 1748 if(limit) 1749 { 1750 timeremaining = limit*60; 1751 gamelimit += timeremaining*1000; 1752 ancmsgft(-1, S_V_OVERTIME, CON_EVENT, "\fyovertime, match extended by \fs\fc%d\fS %s", limit, limit > 1 ? "minutes" : "minute"); 1753 } 1754 else 1755 { 1756 timeremaining = -1; 1757 gamelimit = 0; 1758 ancmsgft(-1, S_V_OVERTIME, CON_EVENT, "\fyovertime, match extended until someone wins"); 1759 } 1760 gamestate = G_S_OVERTIME; 1761 wantsoneminute = false; 1762 } 1763 else 1764 { 1765 ancmsgft(-1, S_V_NOTIFY, CON_EVENT, "\fytime limit has been reached"); 1766 startintermission(); 1767 return; // bail 1768 } 1769 } 1770 if(gs_playing(gamestate) && timeremaining != 0) 1771 { 1772 if(wantsoneminute && timeremaining == 60) ancmsgft(-1, S_V_ONEMINUTE, CON_EVENT, "\fzygone minute remains"); 1773 sendtick(); 1774 } 1775 } 1776 } 1777 if(wasinovertime && !wantsovertime()) 1778 { 1779 ancmsgft(-1, S_V_NOTIFY, CON_EVENT, "\fyovertime has ended, a winner has been chosen"); 1780 startintermission(); 1781 return; // bail 1782 } 1783 if(!m_balance(gamemode, mutators, teamspawns)) 1784 { 1785 int plimit = 0; 1786 if(m_dm(gamemode)) plimit = m_dm_oldschool(gamemode, mutators) ? G(fraglimit) : G(pointlimit); 1787 else if(m_capture(gamemode)) plimit = G(capturelimit); 1788 else if(m_defend(gamemode)) plimit = G(defendlimit) ? G(defendlimit) : INT_MAX-1; 1789 else if(m_bomber(gamemode)) plimit = m_bb_hold(gamemode, mutators) ? G(bomberholdlimit) : G(bomberlimit); 1790 else if(m_race(gamemode) && !m_ra_timed(gamemode, mutators) && !m_ra_gauntlet(gamemode, mutators)) plimit = G(racelimit); 1791 if(plimit) 1792 { 1793 if(m_team(gamemode, mutators)) 1794 { 1795 int best = -1; 1796 loopi(numt) if(best < 0 || teamscore(i+T_FIRST).total > teamscore(best).total) 1797 best = i+T_FIRST; 1798 if(best >= 0 && teamscore(best).total >= plimit) 1799 { 1800 ancmsgft(-1, S_V_NOTIFY, CON_EVENT, "\fyscore limit has been reached"); 1801 startintermission(); 1802 return; // bail 1803 } 1804 } 1805 else 1806 { 1807 int best = -1; 1808 loopv(clients) if(clients[i]->actortype < A_ENEMY && (best < 0 || clients[i]->points > clients[best]->points)) 1809 best = i; 1810 if(best >= 0 && clients[best]->points >= plimit) 1811 { 1812 ancmsgft(-1, S_V_NOTIFY, CON_EVENT, "\fyscore limit has been reached"); 1813 startintermission(); 1814 return; // bail 1815 } 1816 } 1817 } 1818 } 1819 else if(gamelimit > 0 && curbalance < (numt-1)) 1820 { 1821 int delpart = min(gamelimit/(numt*2), G(balancedelay)), balpart = (gamelimit/numt*(curbalance+1))-delpart; 1822 if(gamemillis >= balpart) 1823 { 1824 if(!nextbalance) 1825 { 1826 nextbalance = gamemillis+delpart; 1827 if(delpart >= 1000) 1828 { 1829 int secs = delpart/1000; 1830 ancmsgft(-1, S_V_BALWARN, CON_EVENT, "\fy\fs\fzoyWARNING:\fS \fs\fcteams\fS will be \fs\fcreassigned\fS in \fs\fc%d\fS %s %s", secs, secs != 1 ? "seconds" : "second", m_forcebal(gamemode, mutators) ? "to switch roles" : "for map symmetry"); 1831 } 1832 } 1833 if(gamemillis >= nextbalance && canbalancenow()) 1834 { 1835 int oldbalance = curbalance; 1836 if(++curbalance >= numt) curbalance = 0; // safety first 1837 static vector<clientinfo *> assign[T_TOTAL]; 1838 loopk(T_TOTAL) assign[k].setsize(0); 1839 loopv(clients) if(isteam(gamemode, mutators, clients[i]->team, T_FIRST)) 1840 assign[clients[i]->team-T_FIRST].add(clients[i]); 1841 int scores[T_TOTAL] = {0}; 1842 loopk(numt) scores[k] = teamscore(k+T_FIRST).total; 1843 loopk(numt) 1844 { 1845 int from = mapbals[oldbalance][k], fromt = from-T_FIRST, 1846 to = mapbals[curbalance][k], tot = to-T_FIRST; 1847 loopv(assign[fromt]) 1848 { 1849 clientinfo *cp = assign[fromt][i]; 1850 if(cp->swapteam) 1851 { 1852 loopj(numt) if(mapbals[oldbalance][j] == cp->swapteam) 1853 { 1854 cp->swapteam = mapbals[curbalance][j]; 1855 break; 1856 } 1857 } 1858 if(m_race(gamemode)) 1859 { 1860 cp->cpmillis = 0; 1861 cp->cpnodes.shrink(0); 1862 sendf(-1, 1, "ri3", N_CHECKPOINT, cp->clientnum, -1); 1863 } 1864 setteam(cp, to, (m_balreset(gamemode, mutators) ? TT_RESET : 0)|TT_INFO, false); 1865 cp->lastdeath = 0; 1866 } 1867 score &cs = teamscore(from); 1868 cs.total = scores[tot]; 1869 sendf(-1, 1, "ri3", N_SCORE, cs.team, cs.total); 1870 } 1871 ancmsgft(-1, S_V_BALALERT, CON_EVENT, "\fy\fs\fzoyALERT:\fS \fs\fcteams\fS have %sbeen \fs\fcreassigned\fS %s", delpart > 0 ? "now " : "", m_forcebal(gamemode, mutators) ? "to switch roles" : "for map symmetry"); 1872 if(smode) smode->balance(oldbalance); 1873 mutate(smuts, mut->balance(oldbalance)); 1874 if(smode) smode->layout(); 1875 mutate(smuts, mut->layout()); 1876 nextbalance = 0; 1877 } 1878 } 1879 } 1880 if(m_balteam(gamemode, mutators, 4) && gamestate != G_S_OVERTIME && gamemillis >= G(teambalancewait) && (!lastteambalance || gamemillis >= lastteambalance) && (!nextteambalance || gamemillis >= nextteambalance)) 1881 doteambalance(false); 1882 } 1883 hasitem(int i)1884 bool hasitem(int i) 1885 { 1886 if((m_race(gamemode) && !m_ra_gauntlet(gamemode, mutators)) || m_basic(gamemode, mutators) || !sents.inrange(i) || sents[i].type != WEAPON) return false; 1887 int sweap = m_weapon(A_PLAYER, gamemode, mutators), attr = w_attr(gamemode, mutators, sents[i].type, sents[i].attrs[0], sweap); 1888 if(!isweap(attr) || !w_item(attr, sweap) || !m_check(W(attr, modes), W(attr, muts), gamemode, mutators) || W(attr, disabled)) return false; 1889 if((sents[i].attrs[4] && sents[i].attrs[4] != triggerid) || !m_check(sents[i].attrs[2], sents[i].attrs[3], gamemode, mutators)) return false; 1890 return true; 1891 } 1892 finditem(int i,bool spawned=false,bool carry=false)1893 bool finditem(int i, bool spawned = false, bool carry = false) 1894 { 1895 if(sents[i].spawned) return true; 1896 if(sents[i].type == WEAPON && !(sents[i].attrs[1]&W_F_FORCED)) loopvk(clients) 1897 { 1898 clientinfo *ci = clients[k]; 1899 if(ci->dropped.find(i) && (!spawned || gamemillis < sents[i].millis)) return true; 1900 else if(carry) loopj(W_ALL) 1901 if(ci->online && ci->state == CS_ALIVE && ci->entid[j] == i && ci->hasweap(j, m_weapon(A_PLAYER, gamemode, mutators))) 1902 return spawned; 1903 } 1904 if(spawned && gamemillis < sents[i].millis) return true; 1905 return false; 1906 } 1907 1908 template<class T> sortrandomly(vector<T> & src)1909 void sortrandomly(vector<T> &src) 1910 { 1911 vector<T> dst; 1912 dst.reserve(src.length()); 1913 while(src.length()) dst.add(src.removeunordered(rnd(src.length()))); 1914 src.move(dst); 1915 } 1916 setupitems(bool update)1917 void setupitems(bool update) 1918 { 1919 vector<int> items, enemies; 1920 int sweap = m_weapon(A_PLAYER, gamemode, mutators); 1921 loopv(sents) 1922 { 1923 if(sents[i].type == ACTOR && sents[i].attrs[0] >= 0 && sents[i].attrs[0] < A_TOTAL && (sents[i].attrs[5] == triggerid || !sents[i].attrs[5]) && m_check(sents[i].attrs[3], sents[i].attrs[4], gamemode, mutators)) 1924 { 1925 sents[i].millis = gamemillis+G(enemyspawndelay); 1926 switch(G(enemyspawnstyle) == 3 ? rnd(2)+1 : G(enemyspawnstyle)) 1927 { 1928 case 1: enemies.add(i); break; 1929 case 2: sents[i].millis += (G(enemyspawntime)+rnd(G(enemyspawntime)))/2; break; 1930 default: break; 1931 } 1932 } 1933 else if(m_play(gamemode) && enttype[sents[i].type].usetype == EU_ITEM && hasitem(i)) 1934 { 1935 sents[i].millis = gamemillis+G(itemspawndelay); 1936 switch(G(itemspawnstyle) == 3 ? rnd(2)+1 : G(itemspawnstyle)) 1937 { 1938 case 1: items.add(i); break; 1939 case 2: 1940 { 1941 int attr = w_attr(gamemode, mutators, sents[i].type, sents[i].attrs[0], sweap), delay = sents[i].type == WEAPON && isweap(attr) ? w_spawn(attr) : G(itemspawntime); 1942 if(delay > 1) sents[i].millis += (delay+rnd(delay))/2; 1943 break; 1944 } 1945 default: break; 1946 } 1947 } 1948 } 1949 if(!items.empty()) 1950 { 1951 sortrandomly(items); 1952 loopv(items) sents[items[i]].millis += G(itemspawndelay)*i; 1953 } 1954 if(!enemies.empty()) 1955 { 1956 sortrandomly(enemies); 1957 loopv(enemies) sents[enemies[i]].millis += G(enemyspawndelay)*i; 1958 } 1959 } 1960 setuptriggers(bool update)1961 void setuptriggers(bool update) 1962 { 1963 triggerid = 0; 1964 loopi(TRIGGERIDS+1) triggers[i].reset(i); 1965 if(!update) return; 1966 1967 loopv(sents) if(enttype[sents[i].type].idattr >= 0 && sents[i].attrs[enttype[sents[i].type].idattr] >= 0 && sents[i].attrs[enttype[sents[i].type].idattr] <= TRIGGERIDS) 1968 { 1969 if(enttype[sents[i].type].modesattr >= 0 && !m_check(sents[i].attrs[enttype[sents[i].type].modesattr], sents[i].attrs[enttype[sents[i].type].modesattr+1], gamemode, mutators)) continue; 1970 triggers[sents[i].attrs[enttype[sents[i].type].idattr]].ents.add(i); 1971 } 1972 1973 vector<int> valid; 1974 loopi(TRIGGERIDS) if(!triggers[i+1].ents.empty()) valid.add(triggers[i+1].id); 1975 if(!valid.empty()) triggerid = valid[rnd(valid.length())]; 1976 1977 loopi(TRIGGERIDS) if(triggers[i+1].id != triggerid) loopvk(triggers[i+1].ents) 1978 { 1979 if(sents[triggers[i+1].ents[k]].type != TRIGGER) continue; 1980 bool spawn = sents[triggers[i+1].ents[k]].attrs[4]%2; 1981 if(spawn != sents[triggers[i+1].ents[k]].spawned) 1982 { 1983 sents[triggers[i+1].ents[k]].spawned = spawn; 1984 sents[triggers[i+1].ents[k]].millis = gamemillis; 1985 } 1986 sendf(-1, 1, "ri3", N_TRIGGER, triggers[i+1].ents[k], 1+(spawn ? 2 : 1)); 1987 loopvj(sents[triggers[i+1].ents[k]].kin) if(sents.inrange(sents[triggers[i+1].ents[k]].kin[j])) 1988 { 1989 sents[sents[triggers[i+1].ents[k]].kin[j]].spawned = sents[triggers[i+1].ents[k]].spawned; 1990 sents[sents[triggers[i+1].ents[k]].kin[j]].millis = sents[triggers[i+1].ents[k]].millis; 1991 } 1992 } 1993 } 1994 1995 struct spawn 1996 { 1997 int current, iteration; 1998 vector<int> ents; 1999 vector<int> cycle; 2000 spawnserver::spawn2001 spawn() { reset(); } ~spawnserver::spawn2002 ~spawn() {} 2003 resetserver::spawn2004 void reset() 2005 { 2006 ents.shrink(0); 2007 cycle.shrink(0); 2008 iteration = 0; 2009 current = -1; 2010 } addserver::spawn2011 void add(int n) 2012 { 2013 ents.add(n); 2014 cycle.add(0); 2015 } 2016 } spawns[T_ALL]; 2017 setupspawns(bool update)2018 void setupspawns(bool update) 2019 { 2020 totalspawns = 0; 2021 teamspawns = m_team(gamemode, mutators); 2022 loopi(T_ALL) spawns[i].reset(); 2023 if(update) 2024 { 2025 int numt = numteams(gamemode, mutators), cplayers = 0; 2026 if(m_race(gamemode)) 2027 { 2028 loopv(sents) if(sents[i].type == PLAYERSTART && sents[i].attrs[0] == T_NEUTRAL && (sents[i].attrs[5] == triggerid || !sents[i].attrs[5]) && m_check(sents[i].attrs[3], sents[i].attrs[4], gamemode, mutators)) 2029 { 2030 spawns[m_ra_gauntlet(gamemode, mutators) ? T_ALPHA : T_NEUTRAL].add(i); 2031 totalspawns++; 2032 } 2033 if(!totalspawns) loopv(sents) if(sents[i].type == CHECKPOINT && sents[i].attrs[6] == CP_START && (sents[i].attrs[5] == triggerid || !sents[i].attrs[5]) && m_check(sents[i].attrs[3], sents[i].attrs[4], gamemode, mutators)) 2034 { 2035 spawns[m_ra_gauntlet(gamemode, mutators) ? T_ALPHA : T_NEUTRAL].add(i); 2036 totalspawns++; 2037 } 2038 if(m_ra_gauntlet(gamemode, mutators)) 2039 { 2040 int enemyspawns = 0; 2041 loopv(sents) if(sents[i].type == PLAYERSTART && sents[i].attrs[0] >= T_OMEGA && (sents[i].attrs[5] == triggerid || !sents[i].attrs[5]) && m_check(sents[i].attrs[3], sents[i].attrs[4], gamemode, mutators)) 2042 { 2043 loopk(numt-1) spawns[T_OMEGA+k].add(i); 2044 totalspawns++; 2045 enemyspawns++; 2046 } 2047 if(!enemyspawns) loopv(sents) if(sents[i].type == CHECKPOINT && sents[i].attrs[6] == CP_RESPAWN && (sents[i].attrs[5] == triggerid || !sents[i].attrs[5]) && m_check(sents[i].attrs[3], sents[i].attrs[4], gamemode, mutators)) 2048 { 2049 loopk(numt-1) spawns[T_OMEGA+k].add(i); 2050 totalspawns++; 2051 enemyspawns++; 2052 } 2053 } 2054 setmod(sv_numplayers, 0); 2055 setmod(sv_maxplayers, 0); 2056 return; 2057 } 2058 if(!teamspawns && m_duel(gamemode, mutators)) 2059 { // iterate through teams so players spawn on opposite sides in duel 2060 teamspawns = true; 2061 numt = 2; 2062 } 2063 if(m_play(gamemode) && teamspawns) 2064 { 2065 loopk(3) 2066 { 2067 loopv(sents) if(sents[i].type == PLAYERSTART && (sents[i].attrs[5] == triggerid || !sents[i].attrs[5]) && m_check(sents[i].attrs[3], sents[i].attrs[4], gamemode, mutators)) 2068 { 2069 if(!k && (m_team(gamemode, mutators) ? !isteam(gamemode, mutators, sents[i].attrs[0], T_FIRST) : (sents[i].attrs[0] == T_ALPHA || sents[i].attrs[0] == T_OMEGA))) 2070 continue; 2071 else if(k == 1 && sents[i].attrs[0] == T_NEUTRAL) continue; 2072 else if(k == 2 && sents[i].attrs[0] != T_NEUTRAL) continue; 2073 spawns[k ? T_NEUTRAL : sents[i].attrs[0]].add(i); 2074 totalspawns++; 2075 } 2076 if(totalspawns && m_team(gamemode, mutators)) 2077 { 2078 loopi(numt) if(spawns[i+T_FIRST].ents.empty()) 2079 { 2080 loopj(T_ALL) spawns[j].reset(); 2081 totalspawns = 0; 2082 break; 2083 } 2084 } 2085 if(totalspawns) break; 2086 teamspawns = false; 2087 } 2088 if(totalspawns && teamspawns) 2089 { 2090 int actt = numteams(gamemode, mutators), off = numt-actt; 2091 if(off > 0) loopk(off) 2092 { 2093 int t = T_ALPHA+k*2, v = t+2; 2094 if(isteam(gamemode, mutators, t, T_FIRST) && isteam(gamemode, mutators, v, T_FIRST)) 2095 loopv(spawns[t].ents) spawns[v].add(spawns[t].ents[i]); 2096 } 2097 } 2098 } 2099 if(!totalspawns) 2100 { // use all neutral spawns 2101 teamspawns = false; 2102 loopv(sents) if(sents[i].type == PLAYERSTART && sents[i].attrs[0] == T_NEUTRAL && (sents[i].attrs[5] == triggerid || !sents[i].attrs[5]) && m_check(sents[i].attrs[3], sents[i].attrs[4], gamemode, mutators)) 2103 { 2104 spawns[T_NEUTRAL].add(i); 2105 totalspawns++; 2106 } 2107 } 2108 if(!totalspawns) 2109 { // use all spawns 2110 teamspawns = false; 2111 loopk(2) 2112 { 2113 loopv(sents) if(sents[i].type == PLAYERSTART && (k || ((sents[i].attrs[5] == triggerid || !sents[i].attrs[5]) && m_check(sents[i].attrs[3], sents[i].attrs[4], gamemode, mutators)))) 2114 { 2115 spawns[T_NEUTRAL].add(i); 2116 totalspawns++; 2117 } 2118 if(totalspawns) break; 2119 } 2120 } 2121 2122 if(totalspawns) cplayers = totalspawns/2; 2123 else 2124 { // we can cheat and use weapons for spawns 2125 teamspawns = false; 2126 loopv(sents) if(sents[i].type == WEAPON) 2127 { 2128 spawns[T_NEUTRAL].add(i); 2129 totalspawns++; 2130 } 2131 cplayers = totalspawns/3; 2132 } 2133 if(!m_edit(gamemode)) 2134 { 2135 if(!cplayers) cplayers = totalspawns ? totalspawns : 1; 2136 int np = G(numplayers) ? G(numplayers) : cplayers, mp = G(maxplayers) ? G(maxplayers) : np*3; 2137 if(m_play(gamemode) && m_team(gamemode, mutators)) 2138 { 2139 int offt = np%numt, offq = mp%numt; 2140 if(offt) np += numt-offt; 2141 if(offq) mp += numt-offq; 2142 } 2143 if(mp < np) mp = np; 2144 setmod(sv_numplayers, np); 2145 setmod(sv_maxplayers, mp); 2146 } 2147 } 2148 } 2149 pickspawn(clientinfo * ci)2150 int pickspawn(clientinfo *ci) 2151 { 2152 if(ci->actortype >= A_ENEMY) return ci->spawnpoint; 2153 else 2154 { 2155 if(m_race(gamemode) && !ci->cpnodes.empty() && (!m_ra_gauntlet(gamemode, mutators) || ci->team == T_ALPHA)) 2156 { 2157 int checkpoint = ci->cpnodes.last(); 2158 if(sents.inrange(checkpoint)) return checkpoint; 2159 } 2160 if(totalspawns) 2161 { 2162 int team = T_NEUTRAL, rotate = G(spawnrotate); 2163 if(m_duel(gamemode, mutators) && !m_team(gamemode, mutators)) 2164 { 2165 if(!spawns[T_ALPHA].ents.empty() && !spawns[T_OMEGA].ents.empty()) 2166 team = spawns[T_ALPHA].iteration <= spawns[T_OMEGA].iteration ? T_ALPHA : T_OMEGA; 2167 if(!rotate) rotate = 2; 2168 } 2169 else if(m_play(gamemode) && m_team(gamemode, mutators) && (!m_race(gamemode) || m_ra_gauntlet(gamemode, mutators)) && !spawns[ci->team].ents.empty()) team = ci->team; 2170 else switch(rotate) 2171 { 2172 case 2: 2173 { // random 2174 static vector<int> lowest; 2175 lowest.setsize(0); 2176 loopv(spawns[team].cycle) if(lowest.empty() || spawns[team].cycle[i] <= spawns[team].cycle[lowest[0]]) 2177 { 2178 if(spawns[team].cycle.length() >= 2 && spawns[team].current == i) continue; // avoid using this one again straight away 2179 if(!lowest.empty() && spawns[team].cycle[i] < spawns[team].cycle[lowest[0]]) lowest.setsize(0); 2180 lowest.add(i); 2181 } 2182 if(!lowest.empty()) 2183 { 2184 spawns[team].current = lowest[lowest.length() >= 2 ? rnd(lowest.length()) : 0]; 2185 break; 2186 } 2187 // fall through if this fails.. 2188 } 2189 case 1: 2190 { // sequential 2191 if(++spawns[team].current >= spawns[team].ents.length()) spawns[team].current = 0; 2192 break; 2193 } 2194 case 0: default: spawns[team].current = -1; break; 2195 } 2196 if(spawns[team].ents.inrange(spawns[team].current)) 2197 { 2198 spawns[team].iteration++; 2199 spawns[team].cycle[spawns[team].current]++; 2200 return spawns[team].ents[spawns[team].current]; 2201 } 2202 } 2203 } 2204 return -1; 2205 } 2206 setupgameinfo()2207 void setupgameinfo() 2208 { 2209 setuptriggers(true); 2210 setupitems(true); 2211 setupspawns(true); 2212 mapgameinfo = -1; 2213 hasgameinfo = true; 2214 aiman::poke(); 2215 } 2216 sendspawn(clientinfo * ci)2217 void sendspawn(clientinfo *ci) 2218 { 2219 int weap = -1, health = m_health(gamemode, mutators, ci->actortype); 2220 if(ci->actortype >= A_ENEMY) 2221 { 2222 bool hasent = sents.inrange(ci->spawnpoint) && sents[ci->spawnpoint].type == ACTOR; 2223 if(m_sweaps(gamemode, mutators)) weap = m_weapon(ci->actortype, gamemode, mutators); 2224 else weap = hasent && sents[ci->spawnpoint].attrs[6] > 0 ? sents[ci->spawnpoint].attrs[6]-1 : m_weapon(ci->actortype, gamemode, mutators); 2225 if(!m_insta(gamemode, mutators) && hasent && sents[ci->spawnpoint].attrs[7] > 0) health = max(sents[ci->spawnpoint].attrs[7], 1); 2226 } 2227 int spawn = pickspawn(ci); 2228 ci->spawnstate(gamemode, mutators, weap, health); 2229 ci->updatetimeplayed(); 2230 sendf(ci->clientnum, 1, "ri9i5v", N_SPAWNSTATE, ci->clientnum, spawn, ci->state, ci->points, ci->frags, ci->deaths, ci->totalpoints, ci->totalfrags, ci->totaldeaths, ci->timeplayed, ci->health, ci->cptime, ci->weapselect, W_MAX, &ci->ammo[0]); 2231 ci->lastspawn = gamemillis; 2232 } 2233 2234 template<class T> sendstate(clientinfo * ci,T & p)2235 void sendstate(clientinfo *ci, T &p) 2236 { 2237 ci->updatetimeplayed(); 2238 putint(p, ci->state); 2239 putint(p, ci->points); 2240 putint(p, ci->frags); 2241 putint(p, ci->deaths); 2242 putint(p, ci->totalpoints); 2243 putint(p, ci->totalfrags); 2244 putint(p, ci->totaldeaths); 2245 putint(p, ci->timeplayed); 2246 putint(p, ci->health); 2247 putint(p, ci->cptime); 2248 putint(p, ci->weapselect); 2249 loopi(W_MAX) putint(p, ci->ammo[i]); 2250 } 2251 relayf(int r,const char * s,...)2252 void relayf(int r, const char *s, ...) 2253 { 2254 defvformatbigstring(str, s, s); 2255 ircoutf(r, "%s", str); 2256 #ifdef STANDALONE 2257 bigstring ft; 2258 filterstring(ft, str); 2259 logoutf("%s", ft); 2260 #endif 2261 } 2262 ancmsgft(int cn,int snd,int conlevel,const char * s,...)2263 void ancmsgft(int cn, int snd, int conlevel, const char *s, ...) 2264 { 2265 defvformatbigstring(str, s, s); 2266 if(cn < 0 || allowbroadcast(cn)) sendf(cn, 1, "ri3s", N_ANNOUNCE, snd, conlevel, str); 2267 } 2268 srvmsgft(int cn,int conlevel,const char * s,...)2269 void srvmsgft(int cn, int conlevel, const char *s, ...) 2270 { 2271 defvformatbigstring(str, s, s); 2272 if(cn < 0 || allowbroadcast(cn)) sendf(cn, 1, "ri2s", N_SERVMSG, conlevel, str); 2273 } 2274 srvmsgftforce(int cn,int conlevel,const char * s,...)2275 void srvmsgftforce(int cn, int conlevel, const char *s, ...) 2276 { 2277 defvformatbigstring(str, s, s); 2278 if(cn < 0 || allowbroadcast(cn)) sendf(cn, 1, "ri2s", N_SERVMSG, conlevel, str); 2279 if(cn >= 0 && !allowbroadcast(cn)) sendf(cn, 1, "ri2s", N_SERVMSG, conlevel, str); 2280 } 2281 srvmsgf(int cn,const char * s,...)2282 void srvmsgf(int cn, const char *s, ...) 2283 { 2284 defvformatbigstring(str, s, s); 2285 if(cn < 0 || allowbroadcast(cn)) 2286 { 2287 int conlevel = CON_MESG; 2288 switch(cn) 2289 { 2290 case -3: conlevel = CON_CHAT; cn = -1; break; 2291 case -2: conlevel = CON_EVENT; cn = -1; break; 2292 default: break; 2293 } 2294 sendf(cn, 1, "ri2s", N_SERVMSG, conlevel, str); 2295 } 2296 } 2297 srvoutf(int r,const char * s,...)2298 void srvoutf(int r, const char *s, ...) 2299 { 2300 defvformatbigstring(str, s, s); 2301 srvmsgf(r >= 0 ? -1 : -2, "%s", str); 2302 relayf(abs(r), "%s", str); 2303 } 2304 listdemos(int cn)2305 void listdemos(int cn) 2306 { 2307 packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); 2308 putint(p, N_SENDDEMOLIST); 2309 putint(p, demos.length()); 2310 loopv(demos) 2311 { 2312 sendstring(demos[i].info, p); 2313 putint(p, demos[i].len); 2314 putint(p, demos[i].ctime); 2315 } 2316 sendpacket(cn, 1, p.finalize()); 2317 } 2318 cleardemos(int n)2319 void cleardemos(int n) 2320 { 2321 if(!n) 2322 { 2323 loopv(demos) delete[] demos[i].data; 2324 demos.shrink(0); 2325 srvoutf(4, "\fycleared all demos"); 2326 } 2327 else if(demos.inrange(n-1)) 2328 { 2329 delete[] demos[n-1].data; 2330 demos.remove(n-1); 2331 srvoutf(4, "\fycleared demo \fs\fc%d\fS", n); 2332 } 2333 } 2334 senddemo(int cn,int num,int dni)2335 void senddemo(int cn, int num, int dni) 2336 { 2337 if(!num) num = demos.length(); 2338 if(!demos.inrange(num-1)) return; 2339 demofile &d = demos[num-1]; 2340 sendf(cn, 2, "ri3m", N_SENDDEMO, d.ctime, dni, d.len, d.data); 2341 } 2342 2343 void sendwelcome(clientinfo *ci); 2344 int welcomepacket(packetbuf &p, clientinfo *ci); 2345 enddemoplayback()2346 void enddemoplayback() 2347 { 2348 if(!demoplayback) return; 2349 DELETEP(demoplayback); 2350 loopv(clients) sendf(clients[i]->clientnum, 1, "ri3", N_DEMOPLAYBACK, 0, clients[i]->clientnum); 2351 srvoutf(4, "\fydemo playback finished"); 2352 loopv(clients) sendwelcome(clients[i]); 2353 startintermission(true); 2354 resetgamevars(true); 2355 } 2356 setupdemoplayback()2357 void setupdemoplayback() 2358 { 2359 demoheader hdr; 2360 string msg = ""; 2361 defformatstring(file, strstr(smapname, "maps/")==smapname || strstr(smapname, "maps\\")==smapname ? "%s.dmo" : "demos/%s.dmo", smapname); 2362 demoplayback = opengzfile(file, "rb"); 2363 if(!demoplayback) formatstring(msg, "\frcould not read demo \fs\fc%s\fS", file); 2364 else if(demoplayback->read(&hdr, sizeof(demoheader))!=sizeof(demoheader) || memcmp(hdr.magic, VERSION_DEMOMAGIC, sizeof(hdr.magic))) 2365 formatstring(msg, "\frsorry, \fs\fc%s\fS is not a demo file", file); 2366 else 2367 { 2368 lilswap(&hdr.gamever, 4); 2369 if(hdr.gamever!=VERSION_GAME) 2370 formatstring(msg, "\frdemo \fs\fc%s\fS requires %s version of %s", file, hdr.gamever<VERSION_GAME ? "an older" : "a newer", versionname); 2371 } 2372 if(msg[0]) 2373 { 2374 DELETEP(demoplayback); 2375 srvoutf(4, "%s", msg); 2376 return; 2377 } 2378 2379 srvoutf(4, "\fyplaying demo \fs\fc%s\fS", file); 2380 sendf(-1, 1, "ri3", N_DEMOPLAYBACK, 1, -1); 2381 2382 if(demoplayback->read(&nextplayback, sizeof(nextplayback))!=sizeof(nextplayback)) 2383 { 2384 enddemoplayback(); 2385 return; 2386 } 2387 lilswap(&nextplayback, 1); 2388 } 2389 readdemo()2390 void readdemo() 2391 { 2392 if(!demoplayback || paused) return; 2393 while(gamemillis>=nextplayback) 2394 { 2395 int chan, len; 2396 if(demoplayback->read(&chan, sizeof(chan))!=sizeof(chan) || 2397 demoplayback->read(&len, sizeof(len))!=sizeof(len)) 2398 { 2399 enddemoplayback(); 2400 return; 2401 } 2402 lilswap(&chan, 1); 2403 lilswap(&len, 1); 2404 ENetPacket *packet = enet_packet_create(NULL, len, 0); 2405 if(!packet || demoplayback->read(packet->data, len)!=size_t(len)) 2406 { 2407 if(packet) enet_packet_destroy(packet); 2408 enddemoplayback(); 2409 return; 2410 } 2411 sendpacket(-1, chan, packet); 2412 if(!packet->referenceCount) enet_packet_destroy(packet); 2413 if(!demoplayback) break; 2414 if(demoplayback->read(&nextplayback, sizeof(nextplayback))!=sizeof(nextplayback)) 2415 { 2416 enddemoplayback(); 2417 return; 2418 } 2419 lilswap(&nextplayback, 1); 2420 } 2421 } 2422 prunedemos(int extra=0)2423 void prunedemos(int extra = 0) 2424 { 2425 int n = clamp(demos.length()+extra-G(democount), 0, demos.length()); 2426 if(n <= 0) return; 2427 loopi(n) delete[] demos[i].data; 2428 demos.remove(0, n); 2429 } 2430 2431 struct demoinfo 2432 { 2433 demoheader hdr; 2434 string file; 2435 }; 2436 vector<demoinfo> demoinfos; 2437 vector<char *> faildemos; 2438 scandemo(const char * name)2439 int scandemo(const char *name) 2440 { 2441 if(!name || !*name) return -1; 2442 loopv(demoinfos) if(!strcmp(demoinfos[i].file, name)) return i; 2443 loopv(faildemos) if(!strcmp(faildemos[i], name)) return -1; 2444 stream *f = opengzfile(name, "rb"); 2445 if(!f) 2446 { 2447 faildemos.add(newstring(name)); 2448 return -1; 2449 } 2450 int num = demoinfos.length(); 2451 demoinfo &d = demoinfos.add(); 2452 copystring(d.file, name); 2453 string msg = ""; 2454 if(f->read(&d.hdr, sizeof(demoheader))!=sizeof(demoheader) || memcmp(d.hdr.magic, VERSION_DEMOMAGIC, sizeof(d.hdr.magic))) 2455 formatstring(msg, "\fs\fc%s\fS is not a demo file", name); 2456 else 2457 { 2458 lilswap(&d.hdr.gamever, 4); 2459 if(d.hdr.gamever!=VERSION_GAME) 2460 formatstring(msg, "\frdemo \fs\fc%s\fS requires \fs\fc%s\fS version of %s", name, d.hdr.gamever<VERSION_GAME ? "an older" : "a newer", versionname); 2461 } 2462 delete f; 2463 if(msg[0]) 2464 { 2465 conoutf("%s", msg); 2466 demoinfos.pop(); 2467 faildemos.add(newstring(name)); 2468 return -1; 2469 } 2470 return num; 2471 } 2472 adddemo()2473 void adddemo() 2474 { 2475 if(!demotmp) return; 2476 int len = (int)min(demotmp->size(), stream::offset(G(demomaxsize) + 0x10000)); 2477 demofile &d = demos.add(); 2478 d.ctime = clocktime; 2479 d.data = new uchar[len]; 2480 d.len = len; 2481 formatstring(d.info, "%s on %s", gamename(gamemode, mutators, 0, 32), smapname); 2482 relayf(4, "\fydemo \fs\fc%s\fS recorded \fs\fc%s UTC\fS [\fs\fw%.2f%s\fS]", d.info, gettime(d.ctime, "%Y-%m-%d %H:%M.%S"), d.len > 1024*1024 ? d.len/(1024*1024.f) : d.len/1024.0f, d.len > 1024*1024 ? "MB" : "kB"); 2483 sendf(-1, 1, "ri4s", N_DEMOREADY, demos.length(), d.ctime, d.len, d.info); 2484 demotmp->seek(0, SEEK_SET); 2485 demotmp->read(d.data, len); 2486 DELETEP(demotmp); 2487 if(G(demoautoserversave)) 2488 { 2489 string dafilepath = ""; 2490 if(*filetimeformat) formatstring(dafilepath, "demos/sv_%s_%s-%s.dmo", gettime(d.ctime, filetimeformat), gamename(gamemode, mutators, 1, 32, "_"), smapname); 2491 else formatstring(dafilepath, "demos/sv_%u_%s-%s.dmo", uint(d.ctime), gamename(gamemode, mutators, 1, 32, "_"), smapname); 2492 stream *dafile = openrawfile(dafilepath, "w"); 2493 dafile->write(d.data, d.len); 2494 dafile->close(); 2495 DELETEP(dafile); 2496 } 2497 if(G(demoserverkeeptime)) 2498 { 2499 vector<char *> files; 2500 listfiles("demos", "dmo", files); 2501 loopvrev(files) if(!strncmp(files[i], "sv_", 3)) 2502 { 2503 defformatstring(dirfile, "demos/%s.dmo", files[i]); 2504 int q = scandemo(dirfile); 2505 if(q >= 0 && (clocktime-demoinfos[q].hdr.starttime) >= G(demoserverkeeptime)) 2506 { 2507 const char *fullfile = findfile(dirfile, "r"); 2508 if(fullfile && *fullfile && !unlink(fullfile)) 2509 { 2510 conoutf("deleted old demo: %s", files[i]); 2511 demoinfos.remove(q); 2512 } 2513 } 2514 } 2515 } 2516 } 2517 enddemorecord(bool full)2518 void enddemorecord(bool full) 2519 { 2520 if(!demorecord) return; 2521 DELETEP(demorecord); 2522 if(!demotmp) return; 2523 if(!full && !G(demokeep)) { DELETEP(demotmp); } 2524 else 2525 { 2526 prunedemos(1); 2527 adddemo(); 2528 } 2529 } 2530 writedemo(int chan,void * data,int len)2531 void writedemo(int chan, void *data, int len) 2532 { 2533 if(!demorecord) return; 2534 int stamp[3] = { gamemillis, chan, len }; 2535 lilswap(stamp, 3); 2536 demorecord->write(stamp, sizeof(stamp)); 2537 demorecord->write(data, len); 2538 if(demorecord->rawtell() >= G(demomaxsize)) enddemorecord(!gs_playing(gamestate)); 2539 } 2540 recordpacket(int chan,void * data,int len)2541 void recordpacket(int chan, void *data, int len) 2542 { 2543 writedemo(chan, data, len); 2544 } 2545 setupdemorecord()2546 void setupdemorecord() 2547 { 2548 if(demorecord) enddemorecord(false); 2549 if(m_demo(gamemode) || m_edit(gamemode)) return; 2550 demonextmatch = false; 2551 2552 demotmp = opentempfile("backups/demorecord", "w+b"); 2553 stream *f = opengzfile(NULL, "wb", demotmp); 2554 if(!f) { DELETEP(demotmp); return; } 2555 2556 demorecord = f; 2557 2558 demoheader hdr; 2559 memcpy(hdr.magic, VERSION_DEMOMAGIC, sizeof(hdr.magic)); 2560 hdr.gamever = VERSION_GAME; 2561 hdr.gamemode = gamemode; 2562 hdr.mutators = mutators; 2563 hdr.starttime = clocktime; 2564 lilswap(&hdr.gamever, 4); 2565 copystring(hdr.mapname, smapname); 2566 demorecord->write(&hdr, sizeof(demoheader)); 2567 2568 packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); 2569 welcomepacket(p, NULL); 2570 writedemo(1, p.buf, p.len); 2571 } 2572 endmatch()2573 void endmatch() 2574 { 2575 setpause(false); 2576 checkdemorecord(true); 2577 setmod(sv_botoffset, 0); 2578 if(G(resetmmonend) >= 2) mastermode = MM_OPEN; 2579 if(G(resetvarsonend) >= 2) resetgamevars(false); 2580 if(G(resetallowsonend) >= 2) resetallows(); 2581 if(G(resetbansonend) >= 2) resetbans(); 2582 if(G(resetmutesonend) >= 2) resetmutes(); 2583 if(G(resetlimitsonend) >= 2) resetlimits(); 2584 if(G(resetexceptsonend) >= 2) resetexcepts(); 2585 } 2586 checkvotes(bool force)2587 bool checkvotes(bool force) 2588 { 2589 shouldcheckvotes = false; 2590 int style = gamestate == G_S_VOTING ? G(voteinterm) : G(votestyle); 2591 if(style == 3 && !force) return false; 2592 vector<votecount> votes; 2593 int maxvotes = 0; 2594 loopv(clients) 2595 { 2596 clientinfo *oi = clients[i]; 2597 if(oi->actortype > A_PLAYER) continue; 2598 if(G(votefilter) && !gs_waiting(gamestate) && oi->state == CS_SPECTATOR && !*oi->mapvote) continue; // filter out spectators who haven't voted 2599 maxvotes++; 2600 if(!*oi->mapvote) continue; 2601 if(style == 3) votes.add(votecount(oi->mapvote, oi->modevote, oi->mutsvote)); 2602 else 2603 { 2604 votecount *vc = NULL; 2605 loopvj(votes) if(!strcmp(oi->mapvote, votes[j].map) && oi->modevote == votes[j].mode && oi->mutsvote == votes[j].muts) 2606 { 2607 vc = &votes[j]; 2608 break; 2609 } 2610 if(!vc) vc = &votes.add(votecount(oi->mapvote, oi->modevote, oi->mutsvote)); 2611 vc->count++; 2612 } 2613 } 2614 2615 votecount *best = NULL; 2616 bool passed = force; 2617 if(style == 3) best = !votes.empty() ? &votes[rnd(votes.length())] : NULL; 2618 else 2619 { 2620 int morethanone = 0; 2621 loopv(votes) if(!best || votes[i].count >= best->count) 2622 { 2623 if(best && votes[i].count == best->count) morethanone++; 2624 else morethanone = 0; 2625 best = &votes[i]; 2626 } 2627 if(force && morethanone) 2628 { 2629 int r = rnd(morethanone+1), n = 0; 2630 loopv(votes) if(votes[i].count == best->count) 2631 { 2632 if(n != r) n++; 2633 else { best = &votes[i]; break; } 2634 } 2635 } 2636 if(!passed && best) switch(style) 2637 { 2638 case 2: passed = best->count >= maxvotes; break; 2639 case 1: passed = best->count >= maxvotes*G(votethreshold); break; 2640 case 0: default: break; 2641 } 2642 } 2643 if(passed) 2644 { 2645 sendstats(); 2646 endmatch(); 2647 if(best) 2648 { 2649 srvoutf(-3, "vote passed: \fs\fy%s\fS on \fs\fo%s\fS", gamename(best->mode, best->muts), best->map); 2650 changemap(best->map, best->mode, best->muts); 2651 } 2652 else 2653 { 2654 int mode = G(rotatemode) ? -1 : gamemode, muts = G(rotatemuts) ? -1 : mutators; 2655 changemode(mode, muts); 2656 const char *map = choosemap(smapname, mode, muts); 2657 srvoutf(-3, "server chooses: \fs\fy%s\fS on \fs\fo%s\fS", gamename(mode, muts), map); 2658 changemap(map, mode, muts); 2659 } 2660 return true; 2661 } 2662 return false; 2663 } 2664 mutscmp(int req,int limit)2665 bool mutscmp(int req, int limit) 2666 { 2667 if(req) 2668 { 2669 if(!limit) return false; 2670 loopi(G_M_NUM) if(req&(1<<i) && !(limit&(1<<i))) return false; 2671 } 2672 return true; 2673 } 2674 vote(const char * reqmap,int & reqmode,int & reqmuts,int sender)2675 void vote(const char *reqmap, int &reqmode, int &reqmuts, int sender) 2676 { 2677 clientinfo *ci = (clientinfo *)getinfo(sender); 2678 if(!ci) return; 2679 reqmuts |= G(mutslockforce); 2680 modecheck(reqmode, reqmuts); 2681 if(!m_game(reqmode)) return; 2682 if(!reqmap || !*reqmap) reqmap = "<random>"; 2683 bool israndom = !strcmp(reqmap, "<random>"); 2684 if(m_local(reqmode) && !ci->local) 2685 { 2686 srvmsgft(ci->clientnum, CON_EVENT, "\fraccess denied, you must be a local client to start a %s game", gametype[reqmode].name); 2687 return; 2688 } 2689 bool hasvote = false, hasveto = (mastermode == MM_VETO && haspriv(ci, G(vetolock))) || !numclients(ci->clientnum); 2690 if(!hasveto) 2691 { 2692 if(ci->lastvote && totalmillis-ci->lastvote <= G(votewait)) return; 2693 if(ci->modevote == reqmode && ci->mutsvote == reqmuts && !strcmp(ci->mapvote, reqmap)) return; 2694 } 2695 loopv(clients) 2696 { 2697 clientinfo *oi = clients[i]; 2698 if(oi->actortype > A_PLAYER || !*oi->mapvote || ci == oi) continue; 2699 if(!strcmp(oi->mapvote, reqmap) && oi->modevote == reqmode && oi->mutsvote == reqmuts) 2700 { 2701 hasvote = true; 2702 break; 2703 } 2704 } 2705 if(!hasvote) 2706 { 2707 if(G(votelock)) switch(G(votelocktype)) 2708 { 2709 case 1: if(!haspriv(ci, G(votelock), "vote for a new game")) return; break; 2710 case 2: 2711 if(!israndom && !m_edit(reqmode)) 2712 { 2713 int n = listincludes(sv_previousmaps, reqmap, strlen(reqmap)); 2714 if(n >= 0 && n < G(maphistory) && !haspriv(ci, G(votelock), "vote for a recently played map")) return; 2715 } 2716 break; 2717 case 0: default: break; 2718 } 2719 if(G(modelock)) switch(G(modelocktype)) 2720 { 2721 case 1: if(!haspriv(ci, G(modelock), "change game modes")) return; break; 2722 case 2: if((!((1<<reqmode)&G(modelockfilter)) || !mutscmp(reqmuts, G(mutslockfilter))) && !haspriv(ci, G(modelock), "change to a locked game mode")) return; break; 2723 case 0: default: break; 2724 } 2725 if(reqmode != G_EDITMODE && G(mapslock)) 2726 { 2727 char *list = NULL; 2728 switch(G(mapslocktype)) 2729 { 2730 case 1: 2731 { 2732 list = newstring(G(allowmaps)); 2733 mapcull(list, reqmode, reqmuts, numclients(), G(mapsfilter), true); 2734 break; 2735 } 2736 case 2: 2737 { 2738 maplist(list, reqmode, reqmuts, numclients(), G(mapsfilter), true); 2739 break; 2740 } 2741 case 0: default: break; 2742 } 2743 if(list) 2744 { 2745 if(!israndom && listincludes(list, reqmap, strlen(reqmap)) < 0 && !haspriv(ci, G(modelock), "select maps not in the rotation")) 2746 { 2747 DELETEA(list); 2748 return; 2749 } 2750 DELETEA(list); 2751 } 2752 } 2753 } 2754 copystring(ci->mapvote, reqmap); 2755 ci->modevote = reqmode; 2756 ci->mutsvote = reqmuts; 2757 ci->lastvote = totalmillis ? totalmillis : 1; 2758 if(hasveto) 2759 { 2760 sendstats(); 2761 endmatch(); 2762 srvoutf(-3, "%s forced: \fs\fy%s\fS on \fs\fo%s\fS", colourname(ci), gamename(ci->modevote, ci->mutsvote), ci->mapvote); 2763 changemap(ci->mapvote, ci->modevote, ci->mutsvote); 2764 return; 2765 } 2766 sendf(-1, 1, "ri2si2", N_MAPVOTE, ci->clientnum, ci->mapvote, ci->modevote, ci->mutsvote); 2767 relayf(3, "%s suggests: \fs\fy%s\fS on \fs\fo%s\fS", colourname(ci), gamename(ci->modevote, ci->mutsvote), ci->mapvote); 2768 checkvotes(); 2769 } 2770 scorecmp(clientinfo * ci,uint ip,const char * name,const char * handle,uint clientip)2771 bool scorecmp(clientinfo *ci, uint ip, const char *name, const char *handle, uint clientip) 2772 { 2773 if(ci->handle[0] && !strcmp(handle, ci->handle)) return true; 2774 if(ip && clientip == ip && !strcmp(name, ci->name)) return true; 2775 return false; 2776 } 2777 statsscorecmp(clientinfo * ci,uint ip,const char * name,const char * handle,uint clientip)2778 bool statsscorecmp(clientinfo *ci, uint ip, const char *name, const char *handle, uint clientip) 2779 { 2780 if(ci->handle[0] && !strcmp(handle, ci->handle)) return true; 2781 else if(!ci->handle[0] && ip && clientip == ip && !strcmp(name, ci->name)) return true; 2782 return false; 2783 } 2784 findscore(clientinfo * ci,bool insert)2785 savedscore *findscore(clientinfo *ci, bool insert) 2786 { 2787 uint ip = getclientip(ci->clientnum); 2788 if(!ip && !ci->handle[0]) return NULL; 2789 if(!insert) loopv(clients) 2790 { 2791 clientinfo *oi = clients[i]; 2792 if(oi->clientnum != ci->clientnum && scorecmp(ci, ip, oi->name, oi->handle, getclientip(oi->clientnum))) 2793 { 2794 oi->updatetimeplayed(); 2795 static savedscore curscore; 2796 curscore.save(oi); 2797 return &curscore; 2798 } 2799 } 2800 loopv(savedscores) 2801 { 2802 savedscore &sc = savedscores[i]; 2803 if(scorecmp(ci, ip, sc.name, sc.handle, sc.ip)) return ≻ 2804 } 2805 if(!insert) return NULL; 2806 savedscore &sc = savedscores.add(); 2807 copystring(sc.name, ci->name); 2808 copystring(sc.handle, ci->handle); 2809 sc.ip = ip; 2810 return ≻ 2811 } 2812 findstatsscore(clientinfo * ci,bool insert)2813 savedscore *findstatsscore(clientinfo *ci, bool insert) 2814 { 2815 uint ip = getclientip(ci->clientnum); 2816 if(!ip && !ci->handle[0]) return NULL; 2817 if(!insert) loopv(clients) 2818 { 2819 clientinfo *oi = clients[i]; 2820 if(oi->clientnum != ci->clientnum && statsscorecmp(ci, ip, oi->name, oi->handle, getclientip(oi->clientnum))) 2821 { 2822 oi->updatetimeplayed(); 2823 static savedscore curscore; 2824 curscore.save(oi); 2825 return &curscore; 2826 } 2827 } 2828 loopv(savedstatsscores) 2829 { 2830 savedscore &sc = savedstatsscores[i]; 2831 if(statsscorecmp(ci, ip, sc.name, sc.handle, sc.ip)) return ≻ 2832 } 2833 if(!insert) return NULL; 2834 savedscore &sc = savedstatsscores.add(); 2835 copystring(sc.name, ci->name); 2836 copystring(sc.handle, ci->handle); 2837 sc.ip = ip; 2838 return ≻ 2839 } 2840 givepoints(clientinfo * ci,int points,bool give,bool team=true)2841 void givepoints(clientinfo *ci, int points, bool give, bool team = true) 2842 { 2843 ci->totalpoints += points; 2844 ci->localtotalpoints += points; 2845 if(give) 2846 { 2847 ci->points += points; 2848 sendf(-1, 1, "ri5", N_POINTS, ci->clientnum, points, ci->points, ci->totalpoints); 2849 if(team && m_team(gamemode, mutators) && m_dm(gamemode)) 2850 { 2851 score &ts = teamscore(ci->team); 2852 ts.total += points; 2853 sendf(-1, 1, "ri3", N_SCORE, ts.team, ts.total); 2854 } 2855 } 2856 else if(points) sendf(-1, 1, "ri5", N_POINTS, ci->clientnum, points, ci->points, ci->totalpoints); 2857 } 2858 savescore(clientinfo * ci)2859 void savescore(clientinfo *ci) 2860 { 2861 ci->updatetimeplayed(); 2862 savedscore *sc = findscore(ci, true); 2863 if(sc) 2864 { 2865 if(ci->actortype == A_PLAYER && m_dm(gamemode) && m_team(gamemode, mutators) && !m_nopoints(gamemode, mutators) && G(teamkillrestore) && canplay()) 2866 { 2867 int restorepoints[T_MAX] = {0}; 2868 loopv(ci->teamkills) restorepoints[ci->teamkills[i].team] += ci->teamkills[i].points; 2869 loopi(T_MAX) if(restorepoints[i] >= G(teamkillrestore)) 2870 { 2871 score &ts = teamscore(i); 2872 ts.total += restorepoints[i]; 2873 sendf(-1, 1, "ri3", N_SCORE, ts.team, ts.total); 2874 } 2875 } 2876 sc->save(ci); 2877 } 2878 } 2879 savestatsscore(clientinfo * ci)2880 void savestatsscore(clientinfo *ci) 2881 { 2882 ci->updatetimeplayed(); 2883 savedscore *sc = findstatsscore(ci, true); 2884 if(sc) sc->save(ci); 2885 } 2886 swapteam(clientinfo * ci,int oldteam,int newteam=T_NEUTRAL,bool swaps=true)2887 void swapteam(clientinfo *ci, int oldteam, int newteam = T_NEUTRAL, bool swaps = true) 2888 { 2889 if(ci->swapteam && (!newteam || ci->swapteam == newteam)) ci->swapteam = T_NEUTRAL; 2890 if(!swaps || ci->actortype != A_PLAYER || !oldteam || oldteam == newteam || !m_swapteam(gamemode, mutators)) return; 2891 loopv(clients) if(clients[i] && clients[i] != ci) 2892 { 2893 clientinfo *cp = clients[i]; 2894 if(cp->actortype != A_PLAYER || (newteam && cp->team != newteam) || !cp->swapteam || cp->swapteam != oldteam) continue; 2895 setteam(cp, oldteam, TT_RESET|TT_INFOSM, false); 2896 cp->lastdeath = 0; 2897 ancmsgft(cp->clientnum, S_V_BALALERT, CON_EVENT, "\fyyou have been moved to %s as previously requested", colourteam(oldteam)); 2898 return; 2899 } 2900 if(haspriv(ci, G(teambalancelock))) 2901 { 2902 int worst = -1; 2903 float csk = 0, wsk = 0; 2904 csk = ci->balancescore(); 2905 loopv(clients) if(clients[i] && clients[i] != ci) 2906 { 2907 clientinfo *cp = clients[i]; 2908 if(cp->actortype != A_PLAYER || (newteam && cp->team != newteam)) continue; 2909 float psk = 0; 2910 psk = cp->balancescore(); 2911 if(psk > csk || psk > wsk) continue; 2912 worst = i; 2913 wsk = psk; 2914 } 2915 if(worst >= 0) 2916 { 2917 clientinfo *cp = clients[worst]; 2918 setteam(cp, oldteam, TT_RESET|TT_INFOSM, false); 2919 cp->lastdeath = 0; 2920 ancmsgft(cp->clientnum, S_V_BALALERT, CON_EVENT, "\fyyou have been moved to %s by higher skilled %s %s", colourteam(oldteam), privname(G(teambalancelock)), colourname(ci)); 2921 return; 2922 } 2923 } 2924 } 2925 setteam(clientinfo * ci,int team,int flags,bool swaps)2926 void setteam(clientinfo *ci, int team, int flags, bool swaps) 2927 { 2928 swapteam(ci, ci->team, team, swaps); 2929 if(ci->team != team) 2930 { 2931 bool reenter = false; 2932 if(flags&TT_RESET) waiting(ci, DROP_WEAPONS, false); 2933 else if(flags&TT_SMODE && ci->state == CS_ALIVE) 2934 { 2935 if(smode) smode->leavegame(ci); 2936 mutate(smuts, mut->leavegame(ci)); 2937 reenter = true; 2938 } 2939 ci->lastteam = ci->team; 2940 ci->team = team; 2941 if(reenter) 2942 { 2943 if(smode) smode->entergame(ci); 2944 mutate(smuts, mut->entergame(ci)); 2945 } 2946 if(ci->isready()) aiman::poke(); 2947 } 2948 if(flags&TT_INFO) sendf(-1, 1, "ri3", N_SETTEAM, ci->clientnum, ci->team); 2949 } 2950 2951 struct teamcheck 2952 { 2953 int team; 2954 float score; 2955 int clients; 2956 teamcheckserver::teamcheck2957 teamcheck() : team(T_NEUTRAL), score(0.f), clients(0) {} teamcheckserver::teamcheck2958 teamcheck(int n) : team(n), score(0.f), clients(0) {} teamcheckserver::teamcheck2959 teamcheck(int n, float r) : team(n), score(r), clients(0) {} teamcheckserver::teamcheck2960 teamcheck(int n, int s) : team(n), score(s), clients(0) {} 2961 ~teamcheckserver::teamcheck2962 ~teamcheck() {} 2963 }; 2964 allowteam(clientinfo * ci,int team,int first=T_FIRST,bool check=true)2965 bool allowteam(clientinfo *ci, int team, int first = T_FIRST, bool check = true) 2966 { 2967 if(isteam(gamemode, mutators, team, first)) 2968 { 2969 if(!m_coop(gamemode, mutators)) 2970 { 2971 if(check && m_balteam(gamemode, mutators, 3) && team != chooseteam(ci, team)) return false; 2972 return true; 2973 } 2974 else if(ci->actortype >= A_BOT) return team != mapbals[curbalance][0]; 2975 else return team == mapbals[curbalance][0]; 2976 } 2977 return false; 2978 } 2979 chooseteam(clientinfo * ci,int suggest,bool wantbal)2980 int chooseteam(clientinfo *ci, int suggest, bool wantbal) 2981 { 2982 if(ci->actortype >= A_ENEMY) return T_ENEMY; 2983 else if(m_play(gamemode) && m_team(gamemode, mutators) && ci->state != CS_SPECTATOR && ci->state != CS_EDITING) 2984 { 2985 bool human = ci->actortype == A_PLAYER; 2986 int team = -1, bal = human && !wantbal && (G(teambalance) != 6 || !gs_playing(gamestate)) ? G(teambalance) : 1; 2987 if(human) 2988 { 2989 if(m_coop(gamemode, mutators)) return mapbals[curbalance][0]; 2990 int teams[3][3] = { 2991 { suggest, ci->team, -1 }, 2992 { suggest, ci->team, ci->lastteam }, 2993 { suggest, ci->lastteam, ci->team } 2994 }; 2995 loopi(3) if(allowteam(ci, teams[G(teampersist)][i], T_FIRST, false)) 2996 { 2997 team = teams[G(teampersist)][i]; 2998 if(bal <= 2 && G(teampersist) == 2) return team; 2999 break; 3000 } 3001 } 3002 teamcheck teamchecks[T_TOTAL]; 3003 loopk(T_TOTAL) teamchecks[k].team = T_FIRST+k; 3004 loopv(clients) if(clients[i] != ci) 3005 { 3006 clientinfo *cp = clients[i]; 3007 if(!cp->team || cp->state == CS_SPECTATOR) continue; 3008 if((cp->actortype > A_PLAYER && cp->ownernum < 0) || cp->actortype >= A_ENEMY) continue; 3009 teamcheck &ts = teamchecks[cp->team-T_FIRST]; 3010 if(team > 0 && m_swapteam(gamemode, mutators) && ci->actortype == A_PLAYER && cp->actortype == A_PLAYER && cp->swapteam && ci->team == cp->swapteam && cp->team == team) 3011 return team; // swapteam 3012 if(ci->actortype > A_PLAYER || (ci->actortype == A_PLAYER && cp->actortype == A_PLAYER)) 3013 { // remember: ai just balance teams 3014 ts.score += cp->balancescore(1); 3015 ts.clients++; 3016 } 3017 } 3018 if(bal || team <= 0) loopj(team > 0 ? 2 : 1) 3019 { 3020 teamcheck *worst = NULL; 3021 loopi(numteams(gamemode, mutators)) if(allowteam(ci, teamchecks[i].team, T_FIRST, false)) 3022 { 3023 teamcheck &ts = teamchecks[i]; 3024 switch(bal) 3025 { 3026 case 2: case 5: case 6: 3027 { 3028 if(!worst || (team > 0 && ts.team == team && ts.score <= worst->score) || ts.score < worst->score || ((team <= 0 || worst->team != team) && ts.score == worst->score && ts.clients < worst->clients)) 3029 worst = &ts; 3030 break; 3031 } 3032 case 1: case 3: case 4: default: 3033 { 3034 if(!worst || (team > 0 && ts.team == team && ts.clients <= worst->clients) || ts.clients < worst->clients || ((team <= 0 || worst->team != team) && ts.clients == worst->clients && ts.score < worst->score)) 3035 worst = &ts; 3036 break; 3037 } 3038 } 3039 } 3040 if(worst) 3041 { 3042 vector <int> possibleteams; 3043 loopi(numteams(gamemode, mutators)) if(allowteam(ci, teamchecks[i].team, T_FIRST, false)) 3044 { 3045 teamcheck &ts = teamchecks[i]; 3046 if(ts.score == worst->score && ts.clients == worst->clients) 3047 { 3048 possibleteams.add(ts.team); 3049 } 3050 } 3051 team = possibleteams[rnd(possibleteams.length())]; 3052 break; 3053 } 3054 team = -1; 3055 } 3056 return allowteam(ci, team, T_FIRST, false) ? team : T_ALPHA; 3057 } 3058 return T_NEUTRAL; 3059 } 3060 stopdemo()3061 void stopdemo() 3062 { 3063 if(m_demo(gamemode)) enddemoplayback(); 3064 else checkdemorecord(!gs_playing(gamestate)); 3065 } 3066 3067 void connected(clientinfo *ci); 3068 void welcomeinitclient(clientinfo *ci, packetbuf &p, int exclude = -1, bool nobots = false); 3069 3070 #include "auth.h" 3071 3072 enum { ALST_TRY = 0, ALST_SPAWN, ALST_SPEC, ALST_EDIT, ALST_WALK, ALST_MAX }; 3073 3074 bool getmap(clientinfo *ci = NULL, bool force = false); 3075 crclocked(clientinfo * ci,bool msg=false)3076 bool crclocked(clientinfo *ci, bool msg = false) 3077 { 3078 if(m_play(gamemode) && G(crclock) && ci->actortype == A_PLAYER && (smapcrc ? ci->clientcrc != smapcrc : !ci->clientcrc) && !haspriv(ci, G(crclock))) 3079 { 3080 if(msg) srvmsgft(ci->clientnum, CON_EVENT, "\fyyou are \fs\fccrc locked\fS, please wait for the correct map version.."); 3081 return true; 3082 } 3083 return false; 3084 } 3085 spectator(clientinfo * ci,bool quarantine=false,int sender=-1)3086 void spectator(clientinfo *ci, bool quarantine = false, int sender = -1) 3087 { 3088 if(!ci || ci->actortype > A_PLAYER) return; 3089 ci->state = CS_SPECTATOR; 3090 ci->quarantine = quarantine; 3091 sendf(sender, 1, "ri3", N_SPECTATOR, ci->clientnum, quarantine ? 2 : 1); 3092 setteam(ci, T_NEUTRAL, TT_INFOSM); 3093 } 3094 spectate(clientinfo * ci,bool val,bool quarantine=false)3095 bool spectate(clientinfo *ci, bool val, bool quarantine = false) 3096 { 3097 if(ci->state != CS_SPECTATOR && val) 3098 { 3099 if(ci->state == CS_ALIVE) 3100 { 3101 suicideevent ev; 3102 ev.flags = HIT_SPEC; 3103 ev.process(ci); // process death immediately 3104 } 3105 if(smode) smode->leavegame(ci); 3106 mutate(smuts, mut->leavegame(ci)); 3107 sendf(-1, 1, "ri3", N_SPECTATOR, ci->clientnum, quarantine ? 2 : 1); 3108 ci->state = CS_SPECTATOR; 3109 ci->quarantine = quarantine; 3110 ci->updatetimeplayed(); 3111 setteam(ci, T_NEUTRAL, TT_INFO); 3112 if(ci->isready()) aiman::poke(); 3113 } 3114 else if(ci->state == CS_SPECTATOR && !val) 3115 { 3116 if(crclocked(ci, true)) 3117 { 3118 getmap(ci); 3119 return false; 3120 } 3121 int nospawn = 0; 3122 if(numclients(ci->clientnum, true) >= G(serverclients)) nospawn++; 3123 if(smode && !smode->canspawn(ci, true)) { nospawn++; } 3124 mutate(smuts, if(!mut->canspawn(ci, true)) { nospawn++; }); 3125 ci->state = CS_DEAD; 3126 if(nospawn) 3127 { 3128 spectate(ci, true); 3129 return false; 3130 } 3131 ci->lasttimeplayed = totalmillis ? totalmillis : 1; 3132 ci->lasttimealive = totalmillis ? totalmillis : 1; 3133 ci->lasttimeactive = totalmillis ? totalmillis : 1; 3134 ci->lasttimewielded = totalmillis ? totalmillis : 1; 3135 loopi(W_MAX) ci->lasttimeloadout[i] = totalmillis ? totalmillis : 1; 3136 ci->quarantine = false; 3137 waiting(ci, DROP_RESET); 3138 if(smode) smode->entergame(ci); 3139 mutate(smuts, mut->entergame(ci)); 3140 if(ci->isready()) aiman::poke(); 3141 } 3142 return true; 3143 } 3144 3145 struct clientcrcs 3146 { 3147 int id; 3148 vector<clientinfo *> clients; clientcrcsserver::clientcrcs3149 clientcrcs() {} clientcrcsserver::clientcrcs3150 clientcrcs(int n, clientinfo *m) { id = n; clients.add(m); } ~clientcrcsserver::clientcrcs3151 ~clientcrcs() { clients.setsize(0); } 3152 }; 3153 resetmapdata(bool get=false)3154 void resetmapdata(bool get = false) 3155 { 3156 smapcrc = 0; 3157 mapsending = -1; 3158 loopi(SENDMAP_MAX) if(mapdata[i]) DELETEP(mapdata[i]); 3159 if(get) getmap(); 3160 } 3161 hasmapdata()3162 bool hasmapdata() 3163 { 3164 if(!smapcrc) return false; 3165 loopi(SENDMAP_HAS) if(!mapdata[i]) return false; 3166 return true; 3167 } 3168 getmap(clientinfo * ci,bool force)3169 bool getmap(clientinfo *ci, bool force) 3170 { 3171 if(gs_intermission(gamestate)) return false; // pointless 3172 if(ci && !numclients(ci->clientnum) && !hasmapdata()) 3173 { 3174 ci->wantsmap = false; 3175 sendf(ci->clientnum, 1, "ri", N_FAILMAP); 3176 return false; 3177 } 3178 if(ci) 3179 { 3180 if(mapsending == ci->clientnum) resetmapdata(); 3181 ci->clientcrc = 0; 3182 ci->wantsmap = true; 3183 if(mapsending >= 0) 3184 { 3185 srvmsgft(ci->clientnum, CON_EVENT, "\fythe map is being uploaded, please wait.."); 3186 return true; 3187 } 3188 if(hasmapdata()) 3189 { 3190 if(ci->gettingmap) return true; 3191 ci->gettingmap = true; 3192 srvmsgft(ci->clientnum, CON_EVENT, "\fysending you the map, please wait.."); 3193 loopi(SENDMAP_MAX) if(mapdata[i]) sendfile(ci->clientnum, 2, mapdata[i], "ri3s", N_SENDMAPFILE, i, smapcrc, smapname); 3194 sendwelcome(ci); 3195 ci->needclipboard = 0; 3196 return true; 3197 } 3198 } 3199 if((!force && gs_waiting(gamestate)) || mapsending >= 0 || hasmapdata()) return false; 3200 clientinfo *best = NULL; 3201 if(!m_edit(gamemode) || force) 3202 { 3203 vector<clientcrcs> crcs; 3204 loopv(clients) 3205 { 3206 clientinfo *cs = clients[i]; 3207 if(cs->actortype > A_PLAYER || !cs->name[0] || !cs->online || cs->wantsmap || !cs->clientcrc || !cs->ready) continue; 3208 bool found = false; 3209 loopvj(crcs) if(crcs[j].id == cs->clientcrc) 3210 { 3211 crcs[j].clients.add(cs); 3212 found = true; 3213 break; 3214 } 3215 if(!found) crcs.add(clientcrcs(cs->clientcrc, cs)); 3216 } 3217 int n = -1; 3218 loopv(crcs) if(n < 0 || crcs[n].clients.length() < crcs[i].clients.length()) n = i; 3219 if(n > 0) loopv(crcs[n].clients) 3220 { 3221 clientinfo *cs = crcs[n].clients[i]; 3222 cs->updatetimeplayed(); 3223 if(!best || cs->timeplayed > best->timeplayed) best = cs; 3224 } 3225 } 3226 if(!best) loopv(clients) 3227 { 3228 clientinfo *cs = clients[i]; 3229 if(cs->actortype > A_PLAYER || !cs->name[0] || !cs->online || cs->wantsmap || !cs->ready) continue; 3230 cs->updatetimeplayed(); 3231 if(!best || cs->timeplayed > best->timeplayed) best = cs; 3232 } 3233 if(best) 3234 { 3235 mapsending = best->clientnum; 3236 if(m_edit(gamemode)) 3237 { 3238 smapcrc = 0; 3239 srvoutf(4, "\fythe map is being requested from %s..", colourname(best)); 3240 } 3241 else 3242 { 3243 smapcrc = best->clientcrc; 3244 srvoutf(4, "\fythe map crc \fs\fc0x%.8x\fS is being requested from %s..", smapcrc, colourname(best)); 3245 } 3246 sendf(best->clientnum, 1, "ri", N_GETMAP); 3247 loopv(clients) 3248 { 3249 clientinfo *cs = clients[i]; 3250 if(cs->actortype > A_PLAYER || !cs->name[0] || !cs->online || !cs->ready) continue; 3251 if(cs->wantsmap || crclocked(cs, true)) 3252 { 3253 cs->clientcrc = 0; 3254 cs->wantsmap = true; 3255 spectate(cs, true); 3256 } 3257 } 3258 return true; 3259 } 3260 if(ci) srvmsgft(ci->clientnum, CON_EVENT, "\fysorry, unable to get a map.."); 3261 sendf(-1, 1, "ri", N_FAILMAP); 3262 return false; 3263 } 3264 allowstate(clientinfo * ci,int n,int lock=-1)3265 bool allowstate(clientinfo *ci, int n, int lock = -1) 3266 { 3267 if(!ci) return false; 3268 uint ip = getclientip(ci->clientnum); 3269 switch(n) 3270 { 3271 case ALST_TRY: // try spawn 3272 { 3273 if(ci->quarantine || (ci->state == CS_SPECTATOR && numclients(ci->clientnum, true) >= G(serverclients))) return false; 3274 if(ci->actortype == A_PLAYER) 3275 if(mastermode >= MM_LOCKED && ip && !checkipinfo(control, ipinfo::ALLOW, ip) && !haspriv(ci, lock, "spawn")) 3276 return false; 3277 if(ci->state == CS_ALIVE || ci->state == CS_WAITING) return false; 3278 if(ci->lastdeath && gamemillis-ci->lastdeath <= DEATHMILLIS) return false; 3279 if(crclocked(ci, true)) 3280 { 3281 getmap(ci); 3282 return false; 3283 } 3284 break; 3285 } 3286 case ALST_SPAWN: // spawn 3287 { 3288 if(ci->quarantine || (ci->state == CS_SPECTATOR && numclients(ci->clientnum, true) >= G(serverclients))) return false; 3289 if(ci->state != CS_DEAD && ci->state != CS_WAITING) return false; 3290 if(ci->lastdeath && gamemillis-ci->lastdeath <= DEATHMILLIS) return false; 3291 if(crclocked(ci, true)) 3292 { 3293 getmap(ci); 3294 return false; 3295 } 3296 break; 3297 } 3298 case ALST_SPEC: return ci->actortype == A_PLAYER; // spec 3299 case ALST_WALK: if(ci->state != CS_EDITING) return false; 3300 case ALST_EDIT: // edit on/off 3301 { 3302 if(ci->quarantine || (ci->state == CS_SPECTATOR && numclients(ci->clientnum, true) >= G(serverclients)) || ci->actortype != A_PLAYER || !m_edit(gamemode)) return false; 3303 if(mastermode >= MM_LOCKED && ip && !checkipinfo(control, ipinfo::ALLOW, ip) && !haspriv(ci, lock, "edit")) return false; 3304 break; 3305 } 3306 default: break; 3307 } 3308 return true; 3309 } 3310 sendstats(bool fromintermission)3311 void sendstats(bool fromintermission) 3312 { 3313 if(G(serverstats) && auth::hasstats && !sentstats && gamemillis) 3314 { 3315 loopv(clients) if(clients[i]->actortype == A_PLAYER) savestatsscore(clients[i]); 3316 bool worthy = false; 3317 if(fromintermission) worthy = true; 3318 else if(m_laptime(gamemode, mutators)) 3319 { 3320 loopv(savedstatsscores) if(savedstatsscores[i].actortype == A_PLAYER) if(savedstatsscores[i].cptime > 0) 3321 { 3322 worthy = true; 3323 break; 3324 } 3325 } 3326 if(!worthy) return; 3327 3328 loopv(clients) 3329 { 3330 clients[i]->localtotalpoints -= clients[i]->points; 3331 clients[i]->localtotalfrags -= clients[i]->frags; 3332 clients[i]->localtotaldeaths -= clients[i]->deaths; 3333 } 3334 3335 sentstats = true; 3336 requestmasterf("stats begin\n"); 3337 int unique = 0; 3338 vector<uint> seen; 3339 loopv(savedstatsscores) if(savedstatsscores[i].actortype == A_PLAYER) 3340 { 3341 if((gamemillis / 1000 / 25) >= savedstatsscores[i].timeactive) continue; 3342 if(savedstatsscores[i].handle[0]) 3343 { 3344 seen.add(savedstatsscores[i].ip); 3345 unique += 1; 3346 } 3347 else 3348 { 3349 bool inseen = false; 3350 loopvj(seen) if(seen[j] == savedstatsscores[i].ip) inseen = true; 3351 if(!inseen) 3352 { 3353 seen.add(savedstatsscores[i].ip); 3354 unique += 1; 3355 } 3356 } 3357 } 3358 requestmasterf("stats game %s %d %d %d %d %d\n", escapestring(smapname), gamemode, mutators, gamemillis/1000, unique, m_usetotals(gamemode, mutators) ? 1 : 0); 3359 flushmasteroutput(); 3360 requestmasterf("stats server %s %s %d\n", escapestring(limitstring(G(serverdesc), MAXSDESCLEN+1)), versionstring, serverport); 3361 flushmasteroutput(); 3362 loopi(numteams(gamemode, mutators)) 3363 { 3364 int tp = m_team(gamemode, mutators) ? T_FIRST : T_NEUTRAL; 3365 requestmasterf("stats team %d %d %s\n", i + tp, teamscore(i + tp).total, escapestring(TEAM(i + tp, name))); 3366 } 3367 flushmasteroutput(); 3368 loopv(savedstatsscores) if(savedstatsscores[i].actortype == A_PLAYER) 3369 { 3370 requestmasterf("stats player %s %s %d %d %d %d %d %d\n", 3371 escapestring(savedstatsscores[i].name), escapestring(savedstatsscores[i].handle), 3372 m_laptime(gamemode, mutators) ? savedstatsscores[i].cptime : savedstatsscores[i].points, 3373 savedstatsscores[i].timealive, savedstatsscores[i].frags, savedstatsscores[i].deaths, i, 3374 savedstatsscores[i].timeactive 3375 ); 3376 flushmasteroutput(); 3377 loopj(W_MAX) 3378 { 3379 weaponstats w = savedstatsscores[i].weapstats[j]; 3380 requestmasterf("stats weapon %d %s %s %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n", 3381 i, escapestring(savedstatsscores[i].handle), weaptype[j].name, w.timewielded, w.timeloadout, 3382 w.damage1, w.frags1, w.hits1, w.flakhits1, w.shots1, w.flakshots1, 3383 w.damage2, w.frags2, w.hits2, w.flakhits2, w.shots2, w.flakshots2 3384 ); 3385 flushmasteroutput(); 3386 } 3387 loopvj(savedstatsscores[i].captures) 3388 { 3389 requestmasterf("stats capture %d %s %d %d\n", 3390 i, escapestring(savedstatsscores[i].handle), 3391 savedstatsscores[i].captures[j].capturing, savedstatsscores[i].captures[j].captured); 3392 flushmasteroutput(); 3393 } 3394 loopvj(savedstatsscores[i].bombings) 3395 { 3396 requestmasterf("stats bombing %d %s %d %d\n", 3397 i, escapestring(savedstatsscores[i].handle), 3398 savedstatsscores[i].bombings[j].bombing, savedstatsscores[i].bombings[j].bombed); 3399 flushmasteroutput(); 3400 } 3401 loopvj(savedstatsscores[i].ffarounds) 3402 { 3403 requestmasterf("stats ffaround %d %s %d %d\n", 3404 i, escapestring(savedstatsscores[i].handle), 3405 savedstatsscores[i].ffarounds[j].round, (int)savedstatsscores[i].ffarounds[j].winner); 3406 flushmasteroutput(); 3407 } 3408 } 3409 requestmasterf("stats end\n"); 3410 flushmasteroutput(); 3411 } 3412 } 3413 3414 #include "capturemode.h" 3415 #include "defendmode.h" 3416 #include "bombermode.h" 3417 #include "duelmut.h" 3418 #include "aiman.h" 3419 changemap(const char * name,int mode,int muts)3420 void changemap(const char *name, int mode, int muts) 3421 { 3422 hasgameinfo = shouldcheckvotes = firstblood = sentstats = false; 3423 mapgameinfo = -1; 3424 stopdemo(); 3425 resetmapdata(); 3426 changemode(gamemode = mode, mutators = muts); 3427 curbalance = nextbalance = lastteambalance = nextteambalance = gamemillis = 0; 3428 gamestate = G_S_WAITING; 3429 gamewaittime = 0; 3430 bool hastime = m_play(gamemode) && m_mmvar(gamemode, mutators, timelimit); 3431 oldtimelimit = hastime ? m_mmvar(gamemode, mutators, timelimit) : -1; 3432 timeremaining = hastime ? m_mmvar(gamemode, mutators, timelimit)*60 : -1; 3433 gamelimit = hastime ? timeremaining*1000 : 0; 3434 loopv(savedscores) savedscores[i].mapchange(); 3435 loopv(savedstatsscores) savedstatsscores[i].mapchange(); 3436 setuptriggers(false); 3437 setupspawns(false); 3438 if(smode) smode->reset(); 3439 mutate(smuts, mut->reset()); 3440 smode = NULL; 3441 smuts.shrink(0); 3442 sents.shrink(0); 3443 scores.shrink(0); 3444 aiman::clearai(); 3445 aiman::poke(); 3446 const char *reqmap = name && *name && strcmp(name, "<random>") ? name : pickmap(NULL, gamemode, mutators); 3447 ifserver(reqmap && *reqmap) 3448 { 3449 loopi(SENDMAP_MAX) 3450 { 3451 defformatstring(reqfile, strstr(reqmap, "maps/")==reqmap || strstr(reqmap, "maps\\")==reqmap ? "%s.%s" : "maps/%s.%s", reqmap, sendmaptypes[i]); 3452 if(i == SENDMAP_MPZ) smapcrc = crcfile(reqfile); 3453 mapdata[i] = openfile(reqfile, "rb"); 3454 } 3455 if(!hasmapdata()) resetmapdata(); 3456 } 3457 copystring(smapname, reqmap); 3458 sendf(-1, 1, "risi3", N_MAPCHANGE, smapname, gamemode, mutators, hasmapdata() ? smapcrc : -1); 3459 3460 // server modes 3461 if(m_capture(gamemode)) smode = &capturemode; 3462 else if(m_defend(gamemode)) smode = &defendmode; 3463 else if(m_bomber(gamemode)) smode = &bombermode; 3464 smuts.add(&spawnmutator); 3465 if(m_duke(gamemode, mutators)) smuts.add(&duelmutator); 3466 if(m_vampire(gamemode, mutators)) smuts.add(&vampiremutator); 3467 if(smode) smode->reset(); 3468 mutate(smuts, mut->reset()); 3469 3470 if(m_local(gamemode)) kicknonlocalclients(DISC_PRIVATE); 3471 3472 loopv(clients) 3473 { 3474 clients[i]->mapchange(true); 3475 spectator(clients[i]); 3476 } 3477 3478 if(!demoplayback && m_play(gamemode) && numclients()) 3479 { 3480 vector<char> buf; 3481 buf.put(smapname, strlen(smapname)); 3482 if(*sv_previousmaps && G(maphistory)) 3483 { 3484 vector<char *> prev; 3485 explodelist(sv_previousmaps, prev); 3486 loopvrev(prev) if(!strcmp(prev[i], smapname)) 3487 { 3488 delete[] prev[i]; 3489 prev.remove(i); 3490 } 3491 while(prev.length() >= G(maphistory)) 3492 { 3493 int last = prev.length()-1; 3494 delete[] prev[last]; 3495 prev.remove(last); 3496 } 3497 loopv(prev) 3498 { 3499 buf.add(' '); 3500 buf.put(prev[i], strlen(prev[i])); 3501 } 3502 prev.deletearrays(); 3503 } 3504 buf.add(0); 3505 const char *str = buf.getbuf(); 3506 if(*str) setmods(sv_previousmaps, str); 3507 } 3508 else setmods(sv_previousmaps, ""); 3509 3510 if(numclients()) 3511 { 3512 sendtick(); 3513 if(m_demo(gamemode)) setupdemoplayback(); 3514 else if(demonextmatch) setupdemorecord(); 3515 } 3516 } 3517 checkvar(ident * id,const char * arg)3518 void checkvar(ident *id, const char *arg) 3519 { 3520 if(id && id->flags&IDF_SERVER && id->flags&IDF_GAMEMOD) switch(id->type) 3521 { 3522 case ID_VAR: 3523 { 3524 int ret = parseint(arg); 3525 if(*id->storage.i == id->bin.i) { if(ret != id->bin.i) numgamemods++; } 3526 else if(ret == id->bin.i) numgamemods--; 3527 break; 3528 } 3529 case ID_FVAR: 3530 { 3531 int ret = parsefloat(arg); 3532 if(*id->storage.f == id->bin.f) { if(ret != id->bin.f) numgamemods++; } 3533 else if(ret == id->bin.f) numgamemods--; 3534 break; 3535 } 3536 case ID_SVAR: 3537 { 3538 if(!strcmp(*id->storage.s, id->bin.s)) { if(strcmp(arg, id->bin.s)) numgamemods++; } 3539 else if(!strcmp(arg, id->bin.s)) numgamemods--; 3540 break; 3541 } 3542 default: break; 3543 } 3544 } 3545 servcmd(int nargs,const char * cmd,const char * arg)3546 bool servcmd(int nargs, const char *cmd, const char *arg) 3547 { // incoming commands 3548 #ifndef STANDALONE 3549 if(::connected(false, false)) return false; 3550 #endif 3551 ident *id = idents.access(cmd); 3552 if(id && id->flags&IDF_SERVER) 3553 { 3554 const char *val = NULL; 3555 switch(id->type) 3556 { 3557 case ID_COMMAND: 3558 { 3559 int slen = strlen(id->name); 3560 if(arg && nargs > 1) slen += strlen(arg)+1; 3561 char *s = newstring(slen); 3562 if(nargs <= 1 || !arg) nformatstring(s, slen+1, "%s", id->name); 3563 else nformatstring(s, slen+1, "%s %s", id->name, arg); 3564 char *ret = executestr(s); 3565 conoutft(CON_MESG, "\fy\fs\fc%s\fS returned \fs\fc%s\fS", id->name, ret && *ret ? ret : "failed"); 3566 delete[] s; 3567 delete[] ret; 3568 return true; 3569 } 3570 case ID_VAR: 3571 { 3572 if(nargs <= 1 || !arg) 3573 { 3574 conoutft(CON_MESG, id->flags&IDF_HEX && *id->storage.i >= 0 ? (id->maxval==0xFFFFFF ? "\fy%s = 0x%.6X" : "\fy%s = 0x%X") : "\fy%s = %d", id->name, *id->storage.i); 3575 return true; 3576 } 3577 if(id->maxval < id->minval || id->flags&IDF_READONLY) 3578 { 3579 conoutft(CON_MESG, "\frcannot override variable: %s", id->name); 3580 return true; 3581 } 3582 int ret = parseint(arg); 3583 if(ret < id->minval || ret > id->maxval) 3584 { 3585 conoutft(CON_MESG, 3586 id->flags&IDF_HEX ? 3587 (id->minval <= 255 ? "\frvalid range for %s is %d..0x%X" : "\frvalid range for %s is 0x%X..0x%X") : 3588 "\frvalid range for %s is %d..%d", id->name, id->minval, id->maxval); 3589 return true; 3590 } 3591 if(versioning) 3592 { 3593 id->def.i = ret; 3594 if(versioning == 2) id->bin.i = ret; 3595 } 3596 checkvar(id, arg); 3597 *id->storage.i = ret; 3598 id->changed(); 3599 #ifndef STANDALONE 3600 if(versioning) setvar(&id->name[3], ret, true); 3601 #endif 3602 val = intstr(id); 3603 break; 3604 } 3605 case ID_FVAR: 3606 { 3607 if(nargs <= 1 || !arg) 3608 { 3609 conoutft(CON_MESG, "\fy%s = %s", id->name, floatstr(*id->storage.f)); 3610 return true; 3611 } 3612 if(id->maxvalf < id->minvalf || id->flags&IDF_READONLY) 3613 { 3614 conoutft(CON_MESG, "\frcannot override variable: %s", id->name); 3615 return true; 3616 } 3617 float ret = parsefloat(arg); 3618 if(ret < id->minvalf || ret > id->maxvalf) 3619 { 3620 conoutft(CON_MESG, "\frvalid range for %s is %s..%s", id->name, floatstr(id->minvalf), floatstr(id->maxvalf)); 3621 return true; 3622 } 3623 if(versioning) 3624 { 3625 id->def.f = ret; 3626 if(versioning == 2) id->bin.f = ret; 3627 } 3628 checkvar(id, arg); 3629 *id->storage.f = ret; 3630 id->changed(); 3631 #ifndef STANDALONE 3632 if(versioning) setfvar(&id->name[3], ret, true); 3633 #endif 3634 val = floatstr(*id->storage.f); 3635 break; 3636 } 3637 case ID_SVAR: 3638 { 3639 if(nargs <= 1 || !arg) 3640 { 3641 conoutft(CON_MESG, strchr(*id->storage.s, '"') ? "\fy%s = [%s]" : "\fy%s = \"%s\"", id->name, *id->storage.s); 3642 return true; 3643 } 3644 if(id->flags&IDF_READONLY) 3645 { 3646 conoutft(CON_MESG, "\frcannot override variable: %s", id->name); 3647 return true; 3648 } 3649 if(versioning) 3650 { 3651 delete[] id->def.s; 3652 id->def.s = newstring(arg); 3653 if(versioning == 2) 3654 { 3655 delete[] id->bin.s; 3656 id->bin.s = newstring(arg); 3657 } 3658 } 3659 checkvar(id, arg); 3660 delete[] *id->storage.s; 3661 *id->storage.s = newstring(arg); 3662 id->changed(); 3663 #ifndef STANDALONE 3664 if(versioning) setsvar(&id->name[3], arg, true); 3665 #endif 3666 val = *id->storage.s; 3667 break; 3668 } 3669 default: return false; 3670 } 3671 if(val) 3672 { 3673 sendf(-1, 1, "ri2sis", N_COMMAND, -1, &id->name[3], strlen(val), val); 3674 arg = val; 3675 } 3676 return true; 3677 } 3678 return false; // parse will spit out "unknown command" in this case 3679 } 3680 parsecommand(clientinfo * ci,int nargs,const char * cmd,const char * arg)3681 void parsecommand(clientinfo *ci, int nargs, const char *cmd, const char *arg) 3682 { // incoming commands from clients 3683 defformatstring(cmdname, "sv_%s", cmd); 3684 ident *id = idents.access(cmdname); 3685 if(id && id->flags&IDF_SERVER) 3686 { 3687 const char *name = &id->name[3], *val = NULL, *oldval = NULL; 3688 bool needfreeoldval = false; 3689 int locked = min(max(id->flags&IDF_ADMIN ? int(G(adminlock)) : (id->flags&IDF_MODERATOR ? int(G(moderatorlock)) : 0), G(varslock)), int(PRIV_CREATOR)); 3690 if(!strcmp(id->name, "sv_gamespeed") && G(gamespeedlock) > locked) locked = min(G(gamespeedlock), int(PRIV_CREATOR)); 3691 else if(id->type == ID_VAR) 3692 { 3693 int len = strlen(id->name); 3694 if(len > 4 && !strcmp(&id->name[len-4], "lock")) 3695 locked = min(max(max(*id->storage.i, parseint(arg)), locked), int(PRIV_CREATOR)); 3696 } 3697 #ifndef STANDALONE 3698 if(servertype < 3 && (!strcmp(id->name, "sv_gamespeed") || !strcmp(id->name, "sv_gamepaused"))) locked = PRIV_MAX; 3699 #endif 3700 switch(id->type) 3701 { 3702 case ID_COMMAND: 3703 { 3704 if(locked && !haspriv(ci, locked, "execute that command")) return; 3705 int slen = strlen(id->name); 3706 if(arg && nargs > 1) slen += strlen(arg)+1; 3707 char *s = newstring(slen); 3708 if(nargs <= 1 || !arg) nformatstring(s, slen+1, "%s", id->name); 3709 else nformatstring(s, slen+1, "%s %s", id->name, arg); 3710 char *ret = executestr(s); 3711 srvoutf(-3, "\fy%s executed \fs\fc%s\fS (returned: \fs\fc%s\fS)", colourname(ci), name, ret && * ret ? ret : "failed"); 3712 delete[] s; 3713 delete[] ret; 3714 return; 3715 } 3716 case ID_VAR: 3717 { 3718 if(nargs <= 1 || !arg) 3719 { 3720 srvmsgf(ci->clientnum, id->flags&IDF_HEX && *id->storage.i >= 0 ? (id->maxval==0xFFFFFF ? "\fy%s = 0x%.6X" : "\fy%s = 0x%X") : "\fy%s = %d", name, *id->storage.i); 3721 return; 3722 } 3723 else if(locked && !haspriv(ci, locked, "change that variable")) 3724 { 3725 val = intstr(id); 3726 sendf(ci->clientnum, 1, "ri2sis", N_COMMAND, -1, name, strlen(val), val); 3727 return; 3728 } 3729 if(id->maxval < id->minval || id->flags&IDF_READONLY) 3730 { 3731 srvmsgf(ci->clientnum, "\frcannot override variable: %s", name); 3732 return; 3733 } 3734 int ret = parseint(arg); 3735 if(ret < id->minval || ret > id->maxval) 3736 { 3737 srvmsgf(ci->clientnum, 3738 id->flags&IDF_HEX ? 3739 (id->minval <= 255 ? "\frvalid range for %s is %d..0x%X" : "\frvalid range for %s is 0x%X..0x%X") : 3740 "\frvalid range for %s is %d..%d", name, id->minval, id->maxval); 3741 return; 3742 } 3743 checkvar(id, arg); 3744 oldval = intstr(id); 3745 *id->storage.i = ret; 3746 id->changed(); 3747 val = intstr(id); 3748 break; 3749 } 3750 case ID_FVAR: 3751 { 3752 if(nargs <= 1 || !arg) 3753 { 3754 srvmsgf(ci->clientnum, "\fy%s = %s", name, floatstr(*id->storage.f)); 3755 return; 3756 } 3757 else if(locked && !haspriv(ci, locked, "change that variable")) 3758 { 3759 val = floatstr(*id->storage.f); 3760 sendf(ci->clientnum, 1, "ri2sis", N_COMMAND, -1, name, strlen(val), val); 3761 return; 3762 } 3763 if(id->maxvalf < id->minvalf || id->flags&IDF_READONLY) 3764 { 3765 srvmsgf(ci->clientnum, "\frcannot override variable: %s", name); 3766 return; 3767 } 3768 float ret = parsefloat(arg); 3769 if(ret < id->minvalf || ret > id->maxvalf) 3770 { 3771 srvmsgf(ci->clientnum, "\frvalid range for %s is %s..%s", name, floatstr(id->minvalf), floatstr(id->maxvalf)); 3772 return; 3773 } 3774 checkvar(id, arg); 3775 oldval = floatstr(*id->storage.f); 3776 *id->storage.f = ret; 3777 id->changed(); 3778 val = floatstr(*id->storage.f); 3779 break; 3780 } 3781 case ID_SVAR: 3782 { 3783 if(nargs <= 1 || !arg) 3784 { 3785 srvmsgf(ci->clientnum, strchr(*id->storage.s, '"') ? "\fy%s = [%s]" : "\fy%s = \"%s\"", name, *id->storage.s); 3786 return; 3787 } 3788 else if(locked && !haspriv(ci, locked, "change that variable")) 3789 { 3790 val = *id->storage.s; 3791 sendf(ci->clientnum, 1, "ri2sis", N_COMMAND, -1, name, strlen(val), val); 3792 return; 3793 } 3794 if(id->flags&IDF_READONLY) 3795 { 3796 srvmsgf(ci->clientnum, "\frcannot override variable: %s", name); 3797 return; 3798 } 3799 checkvar(id, arg); 3800 oldval = newstring(*id->storage.s); 3801 needfreeoldval = true; 3802 delete[] *id->storage.s; 3803 *id->storage.s = newstring(arg); 3804 id->changed(); 3805 val = *id->storage.s; 3806 break; 3807 } 3808 default: return; 3809 } 3810 if(val) 3811 { 3812 sendf(-1, 1, "ri2sis", N_COMMAND, ci->clientnum, name, strlen(val), val); 3813 if(oldval) 3814 { 3815 relayf(3, "\fy%s set %s to %s (was: %s)", colourname(ci), name, val, oldval); 3816 if(needfreeoldval) delete[] oldval; 3817 } 3818 else relayf(3, "\fy%s set %s to %s", colourname(ci), name, val); 3819 } 3820 } 3821 else srvmsgf(ci->clientnum, "\frunknown command: %s", cmd); 3822 } 3823 rewritecommand(ident * id,tagval * args,int numargs)3824 bool rewritecommand(ident *id, tagval *args, int numargs) 3825 { 3826 bool found = false; 3827 const char *argstr = numargs > 2 ? conc(&args[1], numargs-1, true) : (numargs > 1 ? args[1].getstr() : ""); 3828 if(id && id->flags&IDF_WORLD && identflags&IDF_WORLD) found = true; 3829 else if(id && id->flags&IDF_SERVER && id->type != ID_COMMAND) found = servcmd(numargs, args[0].s, argstr); 3830 #ifndef STANDALONE 3831 else if(!id || id->flags&IDF_CLIENT) found = client::sendcmd(numargs, args[0].s, argstr); 3832 #endif 3833 if(numargs > 2) delete[] (char *)argstr; 3834 return found; 3835 } 3836 sendservinit(clientinfo * ci)3837 void sendservinit(clientinfo *ci) 3838 { 3839 sendf(ci->clientnum, 1, "ri3ssi", N_SERVERINIT, ci->clientnum, VERSION_GAME, gethostip(ci->clientnum), gethostip(ci->clientnum), ci->sessionid); // TODO proto 231 3840 } 3841 restorescore(clientinfo * ci)3842 bool restorescore(clientinfo *ci) 3843 { 3844 savedscore *sc = findscore(ci, false); 3845 if(sc) 3846 { 3847 sc->restore(ci); 3848 if(ci->actortype == A_PLAYER && m_dm(gamemode) && m_team(gamemode, mutators) && !m_nopoints(gamemode, mutators) && G(teamkillrestore) && canplay()) 3849 { 3850 int restorepoints[T_MAX] = {0}; 3851 loopv(ci->teamkills) restorepoints[ci->teamkills[i].team] += ci->teamkills[i].points; 3852 loopi(T_MAX) if(restorepoints[i] >= G(teamkillrestore)) 3853 { 3854 score &ts = teamscore(i); 3855 ts.total -= restorepoints[i]; 3856 sendf(-1, 1, "ri3", N_SCORE, ts.team, ts.total); 3857 } 3858 } 3859 return true; 3860 } 3861 return false; 3862 } 3863 sendresume(clientinfo * ci,bool reset=false)3864 void sendresume(clientinfo *ci, bool reset = false) 3865 { 3866 ci->updatetimeplayed(); 3867 if(reset) ci->weapreset(false); 3868 sendf(reset ? ci->clientnum : -1, 1, "ri9i4vi", N_RESUME, ci->clientnum, reset ? -1 : ci->state, ci->points, ci->frags, ci->deaths, ci->totalpoints, ci->totalfrags, ci->totaldeaths, ci->timeplayed, ci->health, ci->cptime, ci->weapselect, W_MAX, &ci->ammo[0], -1); 3869 } 3870 putinitclient(clientinfo * ci,packetbuf & p,bool allow)3871 void putinitclient(clientinfo *ci, packetbuf &p, bool allow) 3872 { 3873 if(ci->actortype > A_PLAYER) 3874 { 3875 if(ci->ownernum >= 0) 3876 { 3877 putint(p, N_INITAI); 3878 putint(p, ci->clientnum); 3879 putint(p, ci->ownernum); 3880 putint(p, ci->actortype); 3881 putint(p, ci->spawnpoint); 3882 putint(p, ci->skill); 3883 sendstring(ci->name, p); 3884 putint(p, ci->team); 3885 putint(p, ci->colour); 3886 putint(p, ci->model); 3887 sendstring(ci->vanity, p); 3888 putint(p, ci->loadweap.length()); 3889 loopv(ci->loadweap) putint(p, ci->loadweap[i]); 3890 } 3891 } 3892 else 3893 { 3894 putint(p, N_CLIENTINIT); 3895 putint(p, ci->clientnum); 3896 putint(p, ci->colour); 3897 putint(p, ci->model); 3898 putint(p, ci->checkpointspawn); 3899 putint(p, ci->team); 3900 putint(p, ci->privilege); 3901 sendstring(ci->name, p); 3902 sendstring(ci->vanity, p); 3903 putint(p, ci->loadweap.length()); 3904 loopv(ci->loadweap) putint(p, ci->loadweap[i]); 3905 putint(p, ci->randweap.length()); 3906 loopv(ci->randweap) putint(p, ci->randweap[i]); 3907 sendstring(ci->handle, p); 3908 sendstring(allow ? gethostip(ci->clientnum) : "*", p); // TODO proto 231 3909 sendstring(allow ? gethostip(ci->clientnum) : "*", p); 3910 ci->version.put(p); 3911 } 3912 } 3913 sendinitclient(clientinfo * ci)3914 void sendinitclient(clientinfo *ci) 3915 { 3916 packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE), q(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); 3917 putinitclient(ci, p, true); 3918 p.finalize(); 3919 putinitclient(ci, q, false); 3920 q.finalize(); 3921 loopv(clients) if(clients[i] != ci && allowbroadcast(clients[i]->clientnum)) 3922 sendpacket(clients[i]->clientnum, 1, haspriv(clients[i], G(iphostlock)) ? p.packet : q.packet); 3923 sendpacket(-1, -1, q.packet); // anonymous packet just for recording 3924 } 3925 sendinitclientself(clientinfo * ci)3926 void sendinitclientself(clientinfo *ci) 3927 { 3928 packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); 3929 putinitclient(ci, p, true); 3930 sendpacket(ci->clientnum, 1, p.finalize()); 3931 } 3932 welcomeinitclient(clientinfo * ci,packetbuf & p,int exclude,bool nobots)3933 void welcomeinitclient(clientinfo *ci, packetbuf &p, int exclude, bool nobots) 3934 { 3935 bool iph = ci ? haspriv(ci, G(iphostlock)) : false; 3936 loopv(clients) 3937 { 3938 clientinfo *cp = clients[i]; 3939 if(!cp->connected || cp->clientnum == exclude || (nobots && cp->actortype != A_PLAYER)) continue; 3940 putinitclient(cp, p, iph); 3941 } 3942 } 3943 sendwelcome(clientinfo * ci)3944 void sendwelcome(clientinfo *ci) 3945 { 3946 packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); 3947 int chan = welcomepacket(p, ci); 3948 sendpacket(ci->clientnum, chan, p.finalize()); 3949 } 3950 welcomepacket(packetbuf & p,clientinfo * ci)3951 int welcomepacket(packetbuf &p, clientinfo *ci) 3952 { 3953 putint(p, N_WELCOME); 3954 putint(p, mastermode); 3955 putint(p, N_MAPCHANGE); 3956 sendstring(smapname, p); 3957 putint(p, gamemode); 3958 putint(p, mutators); 3959 if(ci && !ci->online && m_edit(gamemode)) 3960 { 3961 if(numclients(ci->clientnum)) 3962 { 3963 if(mapsending < 0) resetmapdata(); 3964 getmap(ci); 3965 putint(p, -2); // start with an empty map and wait for it 3966 } 3967 else putint(p, -1); // start with an empty map and use it 3968 } 3969 else putint(p, smapcrc); 3970 3971 enumerate(idents, ident, id, { 3972 if(id.flags&IDF_SERVER && !(id.flags&IDF_WORLD)) // reset vars 3973 { 3974 const char *val = NULL; 3975 switch(id.type) 3976 { 3977 case ID_VAR: 3978 { 3979 val = intstr(&id); 3980 break; 3981 } 3982 case ID_FVAR: 3983 { 3984 val = floatstr(*id.storage.f); 3985 break; 3986 } 3987 case ID_SVAR: 3988 { 3989 val = *id.storage.s; 3990 break; 3991 } 3992 default: break; 3993 } 3994 if(val) 3995 { 3996 putint(p, N_COMMAND); 3997 putint(p, -1); 3998 sendstring(&id.name[3], p); 3999 putint(p, strlen(val)); 4000 sendstring(val, p); 4001 } 4002 } 4003 }); 4004 4005 if(!ci || numclients()) 4006 { 4007 putint(p, N_TICK); 4008 putint(p, gamestate); 4009 putint(p, timeleft()); 4010 } 4011 4012 if(hasgameinfo) 4013 { 4014 putint(p, N_GAMEINFO); 4015 loopv(sents) if(enttype[sents[i].type].resyncs) 4016 { 4017 putint(p, i); 4018 if(enttype[sents[i].type].usetype == EU_ITEM) putint(p, finditem(i) ? 1 : 0); 4019 else putint(p, sents[i].spawned ? 1 : 0); 4020 } 4021 putint(p, -1); 4022 } 4023 4024 if(ci) 4025 { 4026 ci->state = CS_SPECTATOR; 4027 ci->team = T_NEUTRAL; 4028 putint(p, N_SPECTATOR); 4029 putint(p, ci->clientnum); 4030 putint(p, ci->quarantine ? 2 : 1); 4031 sendf(-1, 1, "ri3x", N_SPECTATOR, ci->clientnum, ci->quarantine ? 2 : 1, ci->clientnum); 4032 putint(p, N_SETTEAM); 4033 putint(p, ci->clientnum); 4034 putint(p, ci->team); 4035 } 4036 if(!ci || clients.length() > 1) 4037 { 4038 putint(p, N_RESUME); 4039 loopv(clients) 4040 { 4041 clientinfo *oi = clients[i]; 4042 if(ci && oi->clientnum == ci->clientnum) continue; 4043 putint(p, oi->clientnum); 4044 sendstate(oi, p); 4045 } 4046 putint(p, -1); 4047 welcomeinitclient(ci, p, ci ? ci->clientnum : -1); 4048 loopv(clients) 4049 { 4050 clientinfo *oi = clients[i]; 4051 if(oi->actortype > A_PLAYER || (ci && oi->clientnum == ci->clientnum)) continue; 4052 if(*oi->mapvote) 4053 { 4054 putint(p, N_MAPVOTE); 4055 putint(p, oi->clientnum); 4056 sendstring(oi->mapvote, p); 4057 putint(p, oi->modevote); 4058 putint(p, oi->mutsvote); 4059 } 4060 } 4061 } 4062 4063 if(m_team(gamemode, mutators)) loopv(scores) 4064 { 4065 score &cs = scores[i]; 4066 putint(p, N_SCORE); 4067 putint(p, cs.team); 4068 putint(p, cs.total); 4069 } 4070 4071 if(smode) smode->initclient(ci, p, true); 4072 mutate(smuts, mut->initclient(ci, p, true)); 4073 4074 if(ci && !ci->online && *G(servermotd)) 4075 { 4076 putint(p, N_ANNOUNCE); 4077 putint(p, S_GUIACT); 4078 putint(p, CON_CHAT); 4079 sendstring(G(servermotd), p); 4080 } 4081 4082 if(ci) ci->online = true; 4083 return 1; 4084 } 4085 clearevent(clientinfo * ci)4086 void clearevent(clientinfo *ci) { delete ci->events.remove(0); } 4087 addhistory(clientinfo * m,clientinfo * v,int millis)4088 void addhistory(clientinfo *m, clientinfo *v, int millis) 4089 { 4090 bool found = false; 4091 loopv(m->damagelog) if (m->damagelog[i].clientnum == v->clientnum) 4092 { 4093 m->damagelog[i].millis = millis; 4094 found = true; 4095 break; 4096 } 4097 if(!found) m->damagelog.add(dmghist(v->clientnum, millis)); 4098 } 4099 gethistory(clientinfo * m,clientinfo * v,int millis,vector<int> & log,bool clear=false,int points=0)4100 void gethistory(clientinfo *m, clientinfo *v, int millis, vector<int> &log, bool clear = false, int points = 0) 4101 { 4102 loopv(m->damagelog) if(m->damagelog[i].clientnum != v->clientnum && millis-m->damagelog[i].millis <= G(assistkilldelay)) 4103 { 4104 clientinfo *assist = (clientinfo *)getinfo(m->damagelog[i].clientnum); 4105 if(assist) 4106 { 4107 log.add(assist->clientnum); 4108 if(points) givepoints(assist, points, m_points(gamemode, mutators), true); 4109 } 4110 } 4111 if(clear) m->damagelog.shrink(0); 4112 } 4113 isghost(clientinfo * d,clientinfo * e)4114 bool isghost(clientinfo *d, clientinfo *e) 4115 { 4116 if(!e) return false; 4117 if(d->actortype < A_ENEMY && e->actortype < A_ENEMY && m_ghost(gamemode, mutators)) return true; 4118 switch(d->actortype) 4119 { 4120 case A_PLAYER: if(!(AA(e->actortype, collide)&(1<<A_C_PLAYERS))) return true; break; 4121 case A_BOT: if(!(AA(e->actortype, collide)&(1<<A_C_BOTS))) return true; break; 4122 default: if(!(AA(e->actortype, collide)&(1<<A_C_ENEMIES))) return true; break; 4123 } 4124 if(m_team(gamemode, mutators) && d->team == e->team) switch(d->actortype) 4125 { 4126 case A_PLAYER: if(!(AA(e->actortype, teamdamage)&(1<<A_T_PLAYERS))) return true; break; 4127 case A_BOT: if(!(AA(e->actortype, teamdamage)&(1<<A_T_BOTS))) return true; break; 4128 default: if(!(AA(e->actortype, teamdamage)&(1<<A_T_ENEMIES))) return true; break; 4129 } 4130 return false; 4131 } 4132 dodamage(clientinfo * m,clientinfo * v,int damage,int weap,int fromweap,int fromflags,int flags,int material,const ivec & hitpush=ivec (0,0,0),const ivec & hitvel=ivec (0,0,0),float dist=0,bool first=true)4133 void dodamage(clientinfo *m, clientinfo *v, int damage, int weap, int fromweap, int fromflags, int flags, int material, const ivec &hitpush = ivec(0, 0, 0), const ivec &hitvel = ivec(0, 0, 0), float dist = 0, bool first = true) 4134 { 4135 int realdamage = damage, realflags = flags, nodamage = 0, hurt = 0, statweap = fromweap, statalt = WS(fromflags); 4136 realflags &= ~HIT_SFLAGS; 4137 if(realflags&HIT_MATERIAL && (material&MATF_VOLUME) == MAT_LAVA) realflags |= HIT_BURN; 4138 4139 if(smode && !smode->damage(m, v, realdamage, weap, realflags, material, hitpush, hitvel, dist)) { nodamage++; } 4140 mutate(smuts, if(!mut->damage(m, v, realdamage, weap, realflags, material, hitpush, hitvel, dist)) { nodamage++; }); 4141 if(!(realflags&HIT_MATERIAL) && v->actortype < A_ENEMY) 4142 { 4143 if(v == m && !G(damageself)) nodamage++; 4144 else if(isghost(m, v)) nodamage++; 4145 } 4146 4147 if(isweap(weap) && WF(WK(flags), weap, residualundo, WS(flags)) != 0) 4148 { 4149 if(WF(WK(flags), weap, residualundo, WS(flags))&WR(BURN) && m->burning(gamemillis, G(burntime))) 4150 { 4151 m->lastres[WR_BURN] = m->lastrestime[WR_BURN] = 0; 4152 sendf(-1, 1, "ri3", N_SPHY, m->clientnum, SPHY_EXTINGUISH); 4153 } 4154 if(WF(WK(flags), weap, residualundo, WS(flags))&WR(BLEED) && m->bleeding(gamemillis, G(bleedtime))) 4155 m->lastres[WR_BLEED] = m->lastrestime[WR_BLEED] = 0; 4156 if(WF(WK(flags), weap, residualundo, WS(flags))&WR(SHOCK) && m->shocking(gamemillis, G(shocktime))) 4157 m->lastres[WR_SHOCK] = m->lastrestime[WR_SHOCK] = 0; 4158 } 4159 4160 if(nodamage || !hitdealt(realflags)) 4161 { 4162 realflags &= ~HIT_CLEAR; 4163 realflags |= HIT_WAVE; 4164 } 4165 else 4166 { 4167 m->health = min(m->health-realdamage, m_maxhealth(gamemode, mutators, m->actortype)); 4168 if(realdamage > 0) 4169 { 4170 hurt = min(m->health, realdamage); 4171 m->lastregen = m->lastregenamt = 0; 4172 m->lastpain = gamemillis; 4173 v->damage += realdamage; 4174 if(m != v && (!m_team(gamemode, mutators) || m->team != v->team)) 4175 { 4176 if(weap == -1) 4177 { 4178 if(flags&HIT_BURN) 4179 { 4180 statalt = m->lastresalt[WR_BURN]; 4181 statweap = m->lastresweapon[WR_BURN]; 4182 if(isweap(statweap)) 4183 { 4184 if(statalt) v->weapstats[statweap].damage2 += realdamage; 4185 else v->weapstats[statweap].damage1 += realdamage; 4186 } 4187 } 4188 if(flags&HIT_BLEED) 4189 { 4190 statalt = m->lastresalt[WR_BLEED]; 4191 statweap = m->lastresweapon[WR_BLEED]; 4192 if(isweap(statweap)) 4193 { 4194 if(statalt) v->weapstats[statweap].damage2 += realdamage; 4195 else v->weapstats[statweap].damage1 += realdamage; 4196 } 4197 } 4198 if(flags&HIT_SHOCK) 4199 { 4200 statalt = m->lastresalt[WR_SHOCK]; 4201 statweap = m->lastresweapon[WR_SHOCK]; 4202 if(isweap(statweap)) 4203 { 4204 if(statalt) v->weapstats[statweap].damage2 += realdamage; 4205 else v->weapstats[statweap].damage1 += realdamage; 4206 } 4207 } 4208 } 4209 else if(isweap(statweap)) 4210 { 4211 if(statalt) v->weapstats[statweap].damage2 += realdamage; 4212 else v->weapstats[statweap].damage1 += realdamage; 4213 } 4214 } 4215 if(m->health <= 0) realflags |= HIT_KILL; 4216 if(wr_burning(weap, flags)) 4217 { 4218 m->lastres[WR_BURN] = m->lastrestime[WR_BURN] = gamemillis; 4219 m->lastresowner[WR_BURN] = v->clientnum; 4220 m->lastresweapon[WR_BURN] = fromweap; 4221 m->lastresalt[WR_BURN] = statalt; 4222 } 4223 if(wr_bleeding(weap, flags)) 4224 { 4225 m->lastres[WR_BLEED] = m->lastrestime[WR_BLEED] = gamemillis; 4226 m->lastresowner[WR_BLEED] = v->clientnum; 4227 m->lastresweapon[WR_BLEED] = fromweap; 4228 m->lastresalt[WR_BLEED] = statalt; 4229 } 4230 if(wr_shocking(weap, flags)) 4231 { 4232 m->lastres[WR_SHOCK] = m->lastrestime[WR_SHOCK] = gamemillis; 4233 m->lastresowner[WR_SHOCK] = v->clientnum; 4234 m->lastresweapon[WR_SHOCK] = fromweap; 4235 m->lastresalt[WR_SHOCK] = statalt; 4236 } 4237 if(isweap(statweap) && m != v && (!m_team(gamemode, mutators) || m->team != v->team) && first) 4238 { 4239 if(WK(flags)) 4240 { 4241 if(statalt) v->weapstats[statweap].flakhits2++; 4242 else v->weapstats[statweap].flakhits1++; 4243 } 4244 else 4245 { 4246 if(statalt) v->weapstats[statweap].hits2++; 4247 else v->weapstats[statweap].hits1++; 4248 } 4249 } 4250 } 4251 } 4252 if(smode) smode->dodamage(m, v, realdamage, hurt, weap, realflags, material, hitpush, hitvel, dist); 4253 mutate(smuts, mut->dodamage(m, v, realdamage, hurt, weap, realflags, material, hitpush, hitvel, dist)); 4254 if(realdamage >= 0 && m != v && (!m_team(gamemode, mutators) || m->team != v->team)) 4255 addhistory(m, v, gamemillis); 4256 sendf(-1, 1, "ri9i5", N_DAMAGE, m->clientnum, v->clientnum, weap, realflags, realdamage, m->health, hitpush.x, hitpush.y, hitpush.z, hitvel.x, hitvel.y, hitvel.z, int(dist*DNF)); 4257 if(realflags&HIT_KILL) 4258 { 4259 int fragvalue = 1; 4260 if(m != v && (!m_team(gamemode, mutators) || m->team != v->team)) 4261 { 4262 v->frags++; 4263 v->totalfrags++; 4264 v->localtotalfrags++; 4265 if(isweap(statweap)) 4266 { 4267 if(statalt) v->weapstats[statweap].frags2++; 4268 else v->weapstats[statweap].frags1++; 4269 } 4270 } 4271 else fragvalue = -fragvalue; 4272 bool isai = m->actortype >= A_ENEMY, isteamkill = false; 4273 int pointvalue = fragvalue, style = FRAG_NONE; 4274 if(!m_dm_oldschool(gamemode, mutators)) 4275 pointvalue = (smode && !isai ? smode->points(m, v) : fragvalue)*(isai ? G(enemybonus) : G(fragbonus)); 4276 if(realdamage >= (realflags&HIT_EXPLODE ? m_health(gamemode, mutators, m->actortype)/2 : m_health(gamemode, mutators, m->actortype))) 4277 style = FRAG_OBLITERATE; 4278 m->spree = 0; 4279 if(m_team(gamemode, mutators) && v->team == m->team) 4280 { 4281 v->spree = 0; 4282 if(isweap(weap) && (v == m || WF(WK(flags), weap, damagepenalty, WS(flags)) != 0)) 4283 { 4284 if(!m_dm_oldschool(gamemode, mutators)) pointvalue *= G(teamkillpenalty); 4285 if(v != m) isteamkill = true; 4286 } 4287 else pointvalue = 0; // no penalty 4288 } 4289 else if(v != m && v->actortype < A_ENEMY) 4290 { 4291 if(!firstblood && !m_duel(gamemode, mutators) && ((v->actortype == A_PLAYER && m->actortype < A_ENEMY) || (v->actortype < A_ENEMY && m->actortype == A_PLAYER))) 4292 { 4293 firstblood = true; 4294 style |= FRAG_FIRSTBLOOD; 4295 if(!m_dm_oldschool(gamemode, mutators)) pointvalue += G(firstbloodpoints); 4296 } 4297 if(flags&HIT_HEAD) // NOT HZONE 4298 { 4299 style |= FRAG_HEADSHOT; 4300 if(!m_dm_oldschool(gamemode, mutators)) pointvalue += G(headshotpoints); 4301 } 4302 if(m_play(gamemode) && m->actortype < A_ENEMY) 4303 { 4304 int logs = 0; 4305 v->spree++; 4306 v->fraglog.add(m->clientnum); 4307 if(G(multikilldelay)) 4308 { 4309 logs = 0; 4310 loopv(v->fragmillis) 4311 { 4312 if(gamemillis-v->fragmillis[i] > G(multikilldelay)) v->fragmillis.remove(i--); 4313 else logs++; 4314 } 4315 if(!logs) v->rewards[0] &= ~FRAG_MULTI; 4316 v->fragmillis.add(gamemillis); 4317 logs++; 4318 if(logs >= 2) 4319 { 4320 int offset = clamp(logs-2, 0, 2), type = 1<<(FRAG_MKILL+offset); // double, triple, multi.. 4321 if(!(v->rewards[0]&type)) 4322 { 4323 style |= type; 4324 v->rewards[0] |= type; 4325 if(!m_dm_oldschool(gamemode, mutators)) pointvalue += (G(multikillbonus) ? offset+1 : 1)*G(multikillpoints); 4326 } 4327 } 4328 } 4329 loopj(FRAG_SPREES) if(m->rewards[1]&(1<<(FRAG_SPREE+j))) 4330 { 4331 style |= FRAG_BREAKER; 4332 if(!m_dm_oldschool(gamemode, mutators)) pointvalue += G(spreebreaker); 4333 break; 4334 } 4335 if(v->spree <= G(spreecount)*FRAG_SPREES && !(v->spree%G(spreecount))) 4336 { 4337 int offset = clamp((v->spree/G(spreecount)), 1, int(FRAG_SPREES))-1, type = 1<<(FRAG_SPREE+offset); 4338 if(!(v->rewards[0]&type)) 4339 { 4340 style |= type; 4341 loopj(2) v->rewards[j] |= type; 4342 if(!m_dm_oldschool(gamemode, mutators)) pointvalue += G(spreepoints); 4343 } 4344 } 4345 logs = 0; 4346 loopv(m->fraglog) if(m->fraglog[i] == v->clientnum) { logs++; m->fraglog.remove(i--); } 4347 if(logs >= G(dominatecount)) 4348 { 4349 style |= FRAG_REVENGE; 4350 if(!m_dm_oldschool(gamemode, mutators)) pointvalue += G(revengepoints); 4351 } 4352 logs = 0; 4353 loopv(v->fraglog) if(v->fraglog[i] == m->clientnum) logs++; 4354 if(logs == G(dominatecount)) 4355 { 4356 style |= FRAG_DOMINATE; 4357 if(!m_dm_oldschool(gamemode, mutators)) pointvalue += G(dominatepoints); 4358 } 4359 } 4360 } 4361 if(m->actortype < A_ENEMY && m_race(gamemode) && (!m_ra_gauntlet(gamemode, mutators) || m->team == T_ALPHA) && m->cpnodes.length() == 1) 4362 { // reset if hasn't reached another checkpoint yet 4363 m->cpmillis = 0; 4364 m->cpnodes.shrink(0); 4365 sendf(-1, 1, "ri3", N_CHECKPOINT, m->clientnum, -1); 4366 } 4367 if(pointvalue) 4368 { 4369 if(v != m && v->actortype >= A_ENEMY && m->actortype < A_ENEMY) 4370 { 4371 pointvalue = -pointvalue; 4372 givepoints(m, pointvalue, m_points(gamemode, mutators) || m_dm_oldschool(gamemode, mutators), true); 4373 } 4374 else if(v->actortype < A_ENEMY) givepoints(v, pointvalue, m_points(gamemode, mutators) || m_dm_oldschool(gamemode, mutators), true); 4375 } 4376 m->deaths++; 4377 m->totaldeaths++; 4378 m->localtotaldeaths++; 4379 m->rewards[1] = 0; 4380 dropitems(m, actor[m->actortype].living ? DROP_DEATH : DROP_EXPIRE); 4381 static vector<int> dmglog; 4382 dmglog.setsize(0); 4383 gethistory(m, v, gamemillis, dmglog, true, m_dm_oldschool(gamemode, mutators) ? 0 : 1); 4384 sendf(-1, 1, "ri9i5v", N_DIED, m->clientnum, m->deaths, m->totaldeaths, v->clientnum, v->frags, v->totalfrags, v->spree, style, weap, realflags, realdamage, material, dmglog.length(), dmglog.length(), dmglog.getbuf()); 4385 m->position.setsize(0); 4386 if(smode) smode->died(m, v); 4387 mutate(smuts, mut->died(m, v)); 4388 m->updatetimeplayed(); 4389 m->state = CS_DEAD; // don't issue respawn yet until DEATHMILLIS has elapsed 4390 m->lastdeath = gamemillis; 4391 if(m->actortype == A_BOT) aiman::setskill(m); 4392 if(m != v && v->actortype == A_BOT) aiman::setskill(v); 4393 if(isteamkill && v->actortype == A_PLAYER) // don't punish the idiot bots 4394 { 4395 v->teamkills.add(teamkill(totalmillis, v->team, 0-pointvalue)); 4396 if(G(teamkilllock) && !haspriv(v, G(teamkilllock))) 4397 { 4398 int numkills = 0; 4399 if(!G(teamkilltime)) numkills = v->teamkills.length(); 4400 else loopv(v->teamkills) 4401 if(totalmillis-v->teamkills[i].millis <= G(teamkilltime)*1000*60) numkills++; 4402 if(numkills >= G(teamkillwarn) && numkills%G(teamkillwarn) == 0) 4403 { 4404 uint ip = getclientip(v->clientnum); 4405 v->warnings[WARN_TEAMKILL][0]++; 4406 v->warnings[WARN_TEAMKILL][1] = totalmillis ? totalmillis : 1; 4407 if(ip && G(teamkillban) && v->warnings[WARN_TEAMKILL][0] >= G(teamkillban) && !haspriv(v, PRIV_MODERATOR) && !checkipinfo(control, ipinfo::EXCEPT, ip)) 4408 { 4409 ipinfo &c = control.add(); 4410 c.ip = ip; 4411 c.mask = 0xFFFFFFFF; 4412 c.type = ipinfo::BAN; 4413 c.flag = ipinfo::INTERNAL; 4414 c.time = totalmillis ? totalmillis : 1; 4415 c.reason = newstring("team killing is not permitted"); 4416 srvoutf(-3, "\fs\fcbanned\fS %s: %s", colourname(v), c.reason); 4417 updatecontrols = true; 4418 } 4419 else if(G(teamkillkick) && v->warnings[WARN_TEAMKILL][0] >= G(teamkillkick)) 4420 { 4421 srvoutf(-3, "\fs\fckicked\fS %s: team killing is not permitted", colourname(v)); 4422 v->kicked = updatecontrols = true; 4423 } 4424 else srvmsgft(v->clientnum, CON_CHAT, "\fy\fs\fzoyWARNING:\fS team killing is not permitted, action will be taken if you continue"); 4425 } 4426 } 4427 } 4428 } 4429 } 4430 process(clientinfo * ci)4431 void suicideevent::process(clientinfo *ci) 4432 { 4433 if(ci->state != CS_ALIVE) return; 4434 if(flags&HIT_MATERIAL && (material&MATF_VOLUME) == MAT_LAVA) flags |= HIT_BURN; 4435 if(!(flags&HIT_MATERIAL) && !(flags&HIT_LOST) && !(flags&HIT_SPEC)) 4436 { 4437 if(smode && !smode->damage(ci, ci, ci->health, -1, flags, material)) { return; } 4438 mutate(smuts, if(!mut->damage(ci, ci, ci->health, -1, flags, material)) { return; }); 4439 } 4440 ci->spree = 0; 4441 ci->deaths++; 4442 ci->totaldeaths++; 4443 bool kamikaze = dropitems(ci, actor[ci->actortype].living ? DROP_DEATH : DROP_EXPIRE); 4444 if(ci->actortype < A_ENEMY && m_race(gamemode) && (!m_ra_gauntlet(gamemode, mutators) || ci->team == T_ALPHA) && !(flags&HIT_SPEC) && (!flags || ci->cpnodes.length() == 1 || !ci->checkpointspawn)) 4445 { // reset if suicided, hasn't reached another checkpoint yet 4446 ci->cpmillis = 0; 4447 ci->cpnodes.shrink(0); 4448 sendf(-1, 1, "ri3", N_CHECKPOINT, ci->clientnum, -1); 4449 } 4450 else if(!(flags&HIT_LOST) && !(flags&HIT_SPEC)) 4451 { 4452 int pointvalue = -1; 4453 if(!m_dm_oldschool(gamemode, mutators)) 4454 { 4455 pointvalue = (smode ? smode->points(ci, ci) : -1)*G(fragbonus); 4456 if(kamikaze) pointvalue *= G(teamkillpenalty); 4457 } 4458 givepoints(ci, pointvalue, m_points(gamemode, mutators) || m_dm_oldschool(gamemode, mutators), true); 4459 } 4460 if(G(burntime) && flags&HIT_BURN) 4461 { 4462 ci->lastres[WR_BURN] = ci->lastrestime[WR_BURN] = gamemillis; 4463 ci->lastresowner[WR_BURN] = ci->clientnum; 4464 } 4465 if(G(bleedtime) && flags&HIT_BLEED) 4466 { 4467 ci->lastres[WR_BLEED] = ci->lastrestime[WR_BLEED] = gamemillis; 4468 ci->lastresowner[WR_BLEED] = ci->clientnum; 4469 } 4470 if(G(shocktime) && flags&HIT_SHOCK) 4471 { 4472 ci->lastres[WR_SHOCK] = ci->lastrestime[WR_SHOCK] = gamemillis; 4473 ci->lastresowner[WR_SHOCK] = ci->clientnum; 4474 } 4475 static vector<int> dmglog; dmglog.setsize(0); 4476 gethistory(ci, ci, gamemillis, dmglog, true, m_dm_oldschool(gamemode, mutators) ? 0 : 1); 4477 sendf(-1, 1, "ri9i5v", N_DIED, ci->clientnum, ci->deaths, ci->totaldeaths, ci->clientnum, ci->frags, ci->totalfrags, 0, 0, -1, flags, ci->health*2, material, dmglog.length(), dmglog.length(), dmglog.getbuf()); 4478 ci->position.setsize(0); 4479 if(smode) smode->died(ci, NULL); 4480 mutate(smuts, mut->died(ci, NULL)); 4481 ci->updatetimeplayed(); 4482 ci->state = CS_DEAD; 4483 ci->lastdeath = gamemillis; 4484 if(ci->actortype == A_BOT) aiman::setskill(ci); 4485 } 4486 calcdamage(clientinfo * v,clientinfo * m,int weap,int & flags,float radial,float size,float dist,float scale,bool self)4487 int calcdamage(clientinfo *v, clientinfo *m, int weap, int &flags, float radial, float size, float dist, float scale, bool self) 4488 { 4489 flags &= ~HIT_SFLAGS; 4490 if(!hitdealt(flags)) 4491 { 4492 flags &= ~HIT_CLEAR; 4493 flags |= HIT_WAVE; 4494 } 4495 4496 float skew = clamp(scale, 0.f, 1.f)*G(damagescale); 4497 4498 if(flags&HIT_WHIPLASH) skew *= WF(WK(flags), weap, damagewhiplash, WS(flags)); 4499 else if(flags&HIT_HEAD) skew *= WF(WK(flags), weap, damagehead, WS(flags)); 4500 else if(flags&HIT_TORSO) skew *= WF(WK(flags), weap, damagetorso, WS(flags)); 4501 else if(flags&HIT_LEGS) skew *= WF(WK(flags), weap, damagelegs, WS(flags)); 4502 else return 0; 4503 4504 if(radial > 0) skew *= clamp(1.f-dist/size, 1e-6f, 1.f); 4505 else if(WF(WK(flags), weap, taper, WS(flags)) != 0) 4506 skew *= clamp(dist, WF(WK(flags), weap, tapermin, WS(flags)), WF(WK(flags), weap, tapermax, WS(flags))); 4507 4508 if(!m_insta(gamemode, mutators)) 4509 { 4510 if(m_capture(gamemode) && G(capturebuffdelay)) 4511 { 4512 if(v->lastbuff) skew *= G(capturebuffdamage); 4513 if(m->lastbuff) skew /= G(capturebuffshield); 4514 } 4515 else if(m_defend(gamemode) && G(defendbuffdelay)) 4516 { 4517 if(v->lastbuff) skew *= G(defendbuffdamage); 4518 if(m->lastbuff) skew /= G(defendbuffshield); 4519 } 4520 else if(m_bomber(gamemode) && G(bomberbuffdelay)) 4521 { 4522 if(v->lastbuff) skew *= G(bomberbuffdamage); 4523 if(m->lastbuff) skew /= G(bomberbuffshield); 4524 } 4525 } 4526 4527 if(self) 4528 { 4529 float modify = WF(WK(flags), weap, damageself, WS(flags))*G(damageselfscale); 4530 if(modify != 0) skew *= modify; 4531 else 4532 { 4533 flags &= ~HIT_CLEAR; 4534 flags |= HIT_WAVE; 4535 } 4536 } 4537 else if(m_team(gamemode, mutators) && v->team == m->team) 4538 { 4539 float modify = WF(WK(flags), weap, damageteam, WS(flags))*G(damageteamscale); 4540 if(modify != 0) skew *= modify; 4541 else 4542 { 4543 flags &= ~HIT_CLEAR; 4544 flags |= HIT_WAVE; 4545 } 4546 } 4547 4548 return int(ceilf(WF(WK(flags), weap, damage, WS(flags))*skew)); 4549 } 4550 process(clientinfo * ci)4551 void stickyevent::process(clientinfo *ci) 4552 { 4553 if(isweap(weap)) 4554 { 4555 if(!ci->weapshots[weap][WS(flags) ? 1 : 0].find(id)) 4556 { 4557 if(G(serverdebug) >= 2) srvmsgf(ci->clientnum, "sync error: sticky [%d (%d)] failed - not found", weap, id); 4558 return; 4559 } 4560 clientinfo *m = target >= 0 ? (clientinfo *)getinfo(target) : NULL; 4561 if(target < 0 || (m && m->state == CS_ALIVE && !m->protect(gamemillis, m_protect(gamemode, mutators)))) 4562 sendf(-1, 1, "ri9ix", N_STICKY, ci->clientnum, target, id, norm.x, norm.y, norm.z, pos.x, pos.y, pos.z, ci->clientnum); 4563 else if(G(serverdebug) >= 2) srvmsgf(ci->clientnum, "sync error: sticky [%d (%d)] failed - state disallows it", weap, id); 4564 } 4565 } 4566 process(clientinfo * ci)4567 void destroyevent::process(clientinfo *ci) 4568 { 4569 if(weap == -1) 4570 { 4571 ci->dropped.remove(id); 4572 if(sents.inrange(id)) sents[id].millis = gamemillis; 4573 } 4574 else if(isweap(weap)) 4575 { 4576 if(!ci->weapshots[weap][WS(flags) ? 1 : 0].find(id)) 4577 { 4578 if(G(serverdebug) >= 2) srvmsgf(ci->clientnum, "sync error: destroy [%d:%d (%d)] failed - not found", weap, WS(flags) ? 1 : 0, id); 4579 return; 4580 } 4581 vector<clientinfo *> hitclients; 4582 if(hits.empty()) 4583 { 4584 ci->weapshots[weap][WS(flags) ? 1 : 0].remove(id); 4585 if(id >= 0 && !m_insta(gamemode, mutators)) 4586 { 4587 int f = W2(weap, fragweap, WS(flags)); 4588 if(f >= 0) 4589 { 4590 int w = f%W_MAX, r = min(W2(weap, fragrays, WS(flags)), MAXPARAMS); 4591 loopi(r) ci->weapshots[w][f >= W_MAX ? 1 : 0].add(-id); 4592 if(WS(flags)) ci->weapstats[weap].flakshots2 += r; 4593 else ci->weapstats[weap].flakshots1 += r; 4594 } 4595 } 4596 sendf(-1, 1, "ri4x", N_DESTROY, ci->clientnum, 1, id, ci->clientnum); 4597 } 4598 else loopv(hits) 4599 { 4600 bool first = true; 4601 hitset &h = hits[i]; 4602 clientinfo *m = (clientinfo *)getinfo(h.target); 4603 loopvj(hitclients) if(hitclients[j] == m) first = false; 4604 hitclients.add(m); 4605 if(!m) 4606 { 4607 if(G(serverdebug) >= 2) srvmsgf(ci->clientnum, "sync error: destroy [%d (%d)] failed - hit %d [%d] not found", weap, id, i, h.target); 4608 continue; 4609 } 4610 if(h.proj) 4611 { 4612 loopj(W_MAX) loopk(2) if(m->weapshots[j][k].find(h.proj)) 4613 { 4614 sendf(m->clientnum, 1, "ri4", N_DESTROY, m->clientnum, 1, h.proj); 4615 break; 4616 } 4617 } 4618 else 4619 { 4620 int hflags = flags|h.flags; 4621 float skew = float(scale)/DNF, rad = radial > 0 ? clamp(radial/DNF, 0.f, WX(WK(flags), weap, explode, WS(flags), gamemode, mutators, skew)) : 0.f, 4622 size = rad > 0 ? (hflags&HIT_WAVE ? rad*WF(WK(flags), weap, wavepush, WS(flags)) : rad) : 0.f, dist = float(h.dist)/DNF; 4623 if(m->state == CS_ALIVE && !m->protect(gamemillis, m_protect(gamemode, mutators))) 4624 { 4625 int damage = calcdamage(ci, m, weap, hflags, rad, size, dist, skew, ci == m); 4626 if(damage) dodamage(m, ci, damage, weap, fromweap, fromflags, hflags, 0, h.dir, h.vel, dist, first); 4627 else if(G(serverdebug) >= 2) 4628 srvmsgf(ci->clientnum, "sync error: destroy [%d (%d)] failed - hit %d [%d] determined zero damage", weap, id, i, h.target); 4629 } 4630 else if(G(serverdebug) >= 2) 4631 srvmsgf(ci->clientnum, "sync error: destroy [%d (%d)] failed - hit %d [%d] state disallows it", weap, id, i, h.target); 4632 } 4633 } 4634 } 4635 } 4636 process(clientinfo * ci)4637 void shotevent::process(clientinfo *ci) 4638 { 4639 if(!ci->isalive(gamemillis) || !isweap(weap)) 4640 { 4641 if(G(serverdebug) >= 3) srvmsgf(ci->clientnum, "sync error: shoot [%d] failed - unexpected message", weap); 4642 return; 4643 } 4644 int sub = W2(weap, ammosub, WS(flags)); 4645 if(sub > 1 && W2(weap, cooktime, WS(flags))) 4646 { 4647 if(ci->ammo[weap] < sub) 4648 { 4649 int maxscale = int(ci->ammo[weap]/float(sub)*W2(weap, cooktime, WS(flags))); 4650 if(scale > maxscale) scale = maxscale; 4651 } 4652 sub = int(ceilf(sub*scale/float(W2(weap, cooktime, WS(flags))))); 4653 } 4654 if(!ci->canshoot(weap, flags, m_weapon(ci->actortype, gamemode, mutators), millis)) 4655 { 4656 if(!ci->canshoot(weap, flags, m_weapon(ci->actortype, gamemode, mutators), millis, (1<<W_S_RELOAD))) 4657 { 4658 if(sub && W(weap, ammomax)) ci->ammo[weap] = max(ci->ammo[weap]-sub, 0); 4659 if(!ci->hasweap(weap, m_weapon(ci->actortype, gamemode, mutators))) ci->entid[weap] = -1; // its gone.. 4660 if(G(serverdebug)) srvmsgf(ci->clientnum, "sync error: shoot [%d] failed - current state disallows it", weap); 4661 sendresume(ci, true); 4662 return; 4663 } 4664 if(ci->weapload[ci->weapselect] > 0) 4665 { 4666 takeammo(ci, ci->weapselect, ci->weapload[ci->weapselect]); 4667 ci->weapload[ci->weapselect] = -ci->weapload[ci->weapselect]; // the client should already do this for themself 4668 sendf(-1, 1, "ri5x", N_RELOAD, ci->clientnum, ci->weapselect, ci->weapload[ci->weapselect], ci->ammo[ci->weapselect], ci->clientnum); 4669 } 4670 } 4671 takeammo(ci, weap, sub); 4672 ci->setweapstate(weap, WS(flags) ? W_S_SECONDARY : W_S_PRIMARY, W2(weap, delayattack, WS(flags)), millis); 4673 sendf(-1, 1, "ri8ivx", N_SHOTFX, ci->clientnum, weap, flags, scale, from.x, from.y, from.z, shots.length(), shots.length()*sizeof(shotmsg)/sizeof(int), shots.getbuf(), ci->clientnum); 4674 ci->weapshot[weap] = sub; 4675 ci->shotdamage += W2(weap, damage, WS(flags))*shots.length(); 4676 loopv(shots) ci->weapshots[weap][WS(flags) ? 1 : 0].add(shots[i].id); 4677 if(WS(flags)) ci->weapstats[weap].shots2++; 4678 else ci->weapstats[weap].shots1++; 4679 if(!ci->hasweap(weap, m_weapon(ci->actortype, gamemode, mutators))) 4680 { 4681 //if(sents.inrange(ci->entid[weap])) setspawn(ci->entid[weap], false); 4682 sendf(-1, 1, "ri7", N_DROP, ci->clientnum, -1, 1, weap, -1, 0); 4683 ci->ammo[weap] = ci->entid[weap] = -1; // its gone.. 4684 } 4685 } 4686 process(clientinfo * ci)4687 void switchevent::process(clientinfo *ci) 4688 { 4689 if(!ci->isalive(gamemillis) || !isweap(weap)) 4690 { 4691 if(G(serverdebug) >= 3) srvmsgf(ci->clientnum, "sync error: switch [%d] failed - unexpected message", weap); 4692 sendf(ci->clientnum, 1, "ri3", N_WSELECT, ci->clientnum, ci->weapselect); 4693 return; 4694 } 4695 if(!ci->canswitch(weap, m_weapon(ci->actortype, gamemode, mutators), millis, (1<<W_S_SWITCH))) 4696 { 4697 if(!ci->canswitch(weap, m_weapon(ci->actortype, gamemode, mutators), millis, (1<<W_S_SWITCH)|(1<<W_S_RELOAD))) 4698 { 4699 if(G(serverdebug)) srvmsgf(ci->clientnum, "sync error: switch [%d] failed - current state disallows it", weap); 4700 sendresume(ci, true); 4701 return; 4702 } 4703 if(ci->weapload[ci->weapselect] > 0) 4704 { 4705 takeammo(ci, ci->weapselect, ci->weapload[ci->weapselect]); 4706 ci->weapload[ci->weapselect] = -ci->weapload[ci->weapselect]; // the client should already do this for themself 4707 sendf(-1, 1, "ri5x", N_RELOAD, ci->clientnum, ci->weapselect, ci->weapload[ci->weapselect], ci->ammo[ci->weapselect], ci->clientnum); 4708 } 4709 } 4710 ci->updateweaptime(); 4711 ci->weapswitch(weap, millis, G(weaponswitchdelay)); 4712 sendf(-1, 1, "ri3x", N_WSELECT, ci->clientnum, weap, ci->clientnum); 4713 } 4714 process(clientinfo * ci)4715 void dropevent::process(clientinfo *ci) 4716 { 4717 if(!ci->isalive(gamemillis) || !isweap(weap)) 4718 { 4719 if(G(serverdebug) >= 3) srvmsgf(ci->clientnum, "sync error: drop [%d] failed - unexpected message", weap); 4720 return; 4721 } 4722 int sweap = m_weapon(ci->actortype, gamemode, mutators); 4723 if(!ci->candrop(weap, sweap, millis, m_loadout(gamemode, mutators), (1<<W_S_SWITCH))) 4724 { 4725 if(!ci->candrop(weap, sweap, millis, m_loadout(gamemode, mutators), (1<<W_S_SWITCH)|(1<<W_S_RELOAD))) 4726 { 4727 if(G(serverdebug)) srvmsgf(ci->clientnum, "sync error: drop [%d] failed - current state disallows it", weap); 4728 sendresume(ci, true); 4729 return; 4730 } 4731 if(ci->weapload[ci->weapselect] > 0) 4732 { 4733 takeammo(ci, ci->weapselect, ci->weapload[ci->weapselect]); 4734 ci->weapload[ci->weapselect] = -ci->weapload[ci->weapselect]; 4735 sendf(-1, 1, "ri5x", N_RELOAD, ci->clientnum, ci->weapselect, ci->weapload[ci->weapselect], ci->ammo[ci->weapselect], ci->clientnum); 4736 } 4737 } 4738 int dropped = -1, ammo = -1, nweap = ci->bestweap(sweap, true); // switch to best weapon 4739 if(sents.inrange(ci->entid[weap])) 4740 { 4741 dropped = ci->entid[weap]; 4742 ammo = ci->ammo[weap] ? ci->ammo[weap] : W(weap, ammomax); 4743 setspawn(dropped, false); 4744 ci->dropped.add(dropped, ammo); 4745 } 4746 ci->ammo[weap] = ci->entid[weap] = -1; 4747 ci->weapswitch(nweap, millis, G(weaponswitchdelay)); 4748 sendf(-1, 1, "ri7", N_DROP, ci->clientnum, nweap, 1, weap, dropped, ammo); 4749 } 4750 process(clientinfo * ci)4751 void reloadevent::process(clientinfo *ci) 4752 { 4753 if(!ci->isalive(gamemillis) || !isweap(weap)) 4754 { 4755 if(G(serverdebug) >= 3) srvmsgf(ci->clientnum, "sync error: reload [%d] failed - unexpected message", weap); 4756 return; 4757 } 4758 if(!ci->canreload(weap, m_weapon(ci->actortype, gamemode, mutators), false, millis)) 4759 { 4760 if(G(serverdebug)) srvmsgf(ci->clientnum, "sync error: reload [%d] failed - current state disallows it", weap); 4761 sendresume(ci, true); 4762 return; 4763 } 4764 ci->setweapstate(weap, W_S_RELOAD, W(weap, delayreload), millis); 4765 int oldammo = ci->ammo[weap]; 4766 ci->ammo[weap] = min(max(ci->ammo[weap], 0) + W(weap, ammoadd), W(weap, ammomax)); 4767 ci->weapload[weap] = ci->ammo[weap]-oldammo; 4768 sendf(-1, 1, "ri5x", N_RELOAD, ci->clientnum, weap, ci->weapload[weap], ci->ammo[weap], ci->clientnum); 4769 } 4770 process(clientinfo * ci)4771 void useevent::process(clientinfo *ci) 4772 { 4773 if(ci->state != CS_ALIVE || !sents.inrange(ent) || sents[ent].type != WEAPON) 4774 { 4775 if(G(serverdebug) >= 3) srvmsgf(ci->clientnum, "sync error: use [%d] failed - unexpected message", ent); 4776 return; 4777 } 4778 if(!finditem(ent)) 4779 { 4780 if(G(serverdebug)) srvmsgf(ci->clientnum, "sync error: use [%d] failed - doesn't seem to be spawned anywhere", ent); 4781 return; 4782 } 4783 ci->updateweaptime(); 4784 int sweap = m_weapon(ci->actortype, gamemode, mutators), attr = w_attr(gamemode, mutators, sents[ent].type, sents[ent].attrs[0], sweap); 4785 if(!isweap(attr)) return; 4786 if(!ci->canuse(sents[ent].type, attr, sents[ent].attrs, sweap, millis, (1<<W_S_SWITCH))) 4787 { 4788 if(!ci->canuse(sents[ent].type, attr, sents[ent].attrs, sweap, millis, (1<<W_S_SWITCH)|(1<<W_S_RELOAD))) 4789 { 4790 if(G(serverdebug)) srvmsgf(ci->clientnum, "sync error: use [%d] failed - current state disallows it", ent); 4791 sendresume(ci, true); 4792 return; 4793 } 4794 if(ci->weapload[ci->weapselect] > 0) 4795 { 4796 takeammo(ci, ci->weapselect, ci->weapload[ci->weapselect]); 4797 ci->weapload[ci->weapselect] = -ci->weapload[ci->weapselect]; // the client should already do this for themself 4798 sendf(-1, 1, "ri5x", N_RELOAD, ci->clientnum, ci->weapselect, ci->weapload[ci->weapselect], ci->ammo[ci->weapselect], ci->clientnum); 4799 } 4800 } 4801 int weap = -1, ammoamt = -1, dropped = -1, ammo = -1; 4802 if(m_classic(gamemode, mutators) && !ci->hasweap(attr, sweap) && w_carry(attr, sweap) && ci->carry(sweap) >= AA(ci->actortype, maxcarry)) weap = ci->drop(sweap); 4803 loopvk(clients) if(clients[k]->dropped.find(ent)) 4804 { 4805 clients[k]->dropped.values(ent, ammoamt); 4806 break; 4807 } 4808 if(isweap(weap)) 4809 { 4810 if(sents.inrange(ci->entid[weap])) 4811 { 4812 dropped = ci->entid[weap]; 4813 ammo = ci->ammo[weap]; 4814 setspawn(dropped, false); 4815 ci->setweapstate(weap, W_S_SWITCH, G(weaponswitchdelay), millis); 4816 ci->dropped.add(dropped, ammo); 4817 } 4818 ci->ammo[weap] = ci->entid[weap] = -1; 4819 } 4820 setspawn(ent, false, true); 4821 ci->useitem(ent, sents[ent].type, attr, ammoamt, sweap, millis, G(weaponswitchdelay)); 4822 sendf(-1, 1, "ri8", N_ITEMACC, ci->clientnum, ent, ammoamt, sents[ent].spawned ? 1 : 0, weap, dropped, ammo); 4823 } 4824 flush(clientinfo * ci,int fmillis)4825 bool gameevent::flush(clientinfo *ci, int fmillis) 4826 { 4827 process(ci); 4828 return true; 4829 } 4830 flush(clientinfo * ci,int fmillis)4831 bool timedevent::flush(clientinfo *ci, int fmillis) 4832 { 4833 if(millis > fmillis) return false; 4834 else if(millis >= ci->lastevent) 4835 { 4836 ci->lastevent = millis; 4837 process(ci); 4838 } 4839 return true; 4840 } 4841 flushevents(clientinfo * ci,int millis)4842 void flushevents(clientinfo *ci, int millis) 4843 { 4844 while(ci->events.length()) 4845 { 4846 gameevent *ev = ci->events[0]; 4847 if(ev->flush(ci, millis)) clearevent(ci); 4848 else break; 4849 } 4850 } 4851 processevents()4852 void processevents() 4853 { 4854 loopv(clients) 4855 { 4856 clientinfo *ci = clients[i]; 4857 flushevents(ci, gamemillis); 4858 } 4859 } 4860 cleartimedevents(clientinfo * ci)4861 void cleartimedevents(clientinfo *ci) 4862 { 4863 int keep = 0; 4864 loopv(ci->events) 4865 { 4866 if(ci->events[i]->keepable()) 4867 { 4868 if(keep < i) 4869 { 4870 for(int j = keep; j < i; j++) delete ci->events[j]; 4871 ci->events.remove(keep, i - keep); 4872 i = keep; 4873 } 4874 keep = i+1; 4875 continue; 4876 } 4877 } 4878 while(ci->events.length() > keep) delete ci->events.pop(); 4879 } 4880 requestswap(clientinfo * ci,int team)4881 int requestswap(clientinfo *ci, int team) 4882 { 4883 if(!allowteam(ci, team, T_FIRST, numclients() > 1)) 4884 { 4885 if(team && m_swapteam(gamemode, mutators) && ci->team != team && ci->actortype == A_PLAYER && ci->swapteam != team && canplay()) 4886 { 4887 ancmsgft(-1, S_V_NOTIFY, CON_EVENT, "\fy%s requests swap to team %s, change teams to accept", colourname(ci), colourteam(team)); 4888 ci->swapteam = team; 4889 } 4890 team = chooseteam(ci); 4891 } 4892 return team; 4893 } 4894 waiting(clientinfo * ci,int drop,bool doteam,bool exclude)4895 void waiting(clientinfo *ci, int drop, bool doteam, bool exclude) 4896 { 4897 ci->updatetimeplayed(); 4898 if(ci->state == CS_ALIVE) 4899 { 4900 if(drop) dropitems(ci, drop); 4901 if(smode) smode->died(ci); 4902 mutate(smuts, mut->died(ci)); 4903 ci->lastdeath = gamemillis; 4904 } 4905 //else if(!ci->lastdeath) ci->lastdeath = gamemillis; 4906 if(exclude) sendf(-1, 1, "ri2x", N_WAITING, ci->clientnum, ci->clientnum); 4907 else sendf(-1, 1, "ri2", N_WAITING, ci->clientnum); 4908 ci->state = CS_WAITING; 4909 ci->weapreset(false); 4910 if(doteam && !allowteam(ci, ci->team, T_FIRST, false)) setteam(ci, chooseteam(ci), TT_INFO); 4911 } 4912 triggertime(int i)4913 int triggertime(int i) 4914 { 4915 if(sents.inrange(i)) switch(sents[i].type) 4916 { 4917 case TRIGGER: case MAPMODEL: case PARTICLES: case MAPSOUND: case TELEPORT: case PUSHER: return 1000; break; 4918 default: break; 4919 } 4920 return 0; 4921 } 4922 checkents()4923 void checkents() 4924 { 4925 loopv(sents) switch(sents[i].type) 4926 { 4927 case TRIGGER: 4928 { 4929 if(sents[i].attrs[1] == TR_LINK && sents[i].spawned && gamemillis >= sents[i].millis && (sents[i].attrs[4] == triggerid || !sents[i].attrs[4]) && m_check(sents[i].attrs[5], sents[i].attrs[6], gamemode, mutators)) 4930 { 4931 sents[i].spawned = false; 4932 sents[i].millis = gamemillis+(triggertime(i)*2); 4933 sendf(-1, 1, "ri3", N_TRIGGER, i, 0); 4934 loopvj(sents[i].kin) if(sents.inrange(sents[i].kin[j])) 4935 { 4936 if(sents[sents[i].kin[j]].type == TRIGGER && !m_check(sents[sents[i].kin[j]].attrs[5], sents[sents[i].kin[j]].attrs[6], gamemode, mutators)) 4937 continue; 4938 sents[sents[i].kin[j]].spawned = sents[i].spawned; 4939 sents[sents[i].kin[j]].millis = sents[i].millis; 4940 } 4941 } 4942 break; 4943 } 4944 default: 4945 { 4946 if(enttype[sents[i].type].usetype != EU_ITEM) break; 4947 bool allowed = hasitem(i); 4948 if((allowed && !sents[i].spawned && !finditem(i, true, true)) || (!allowed && sents[i].spawned)) 4949 setspawn(i, allowed, true, true); 4950 break; 4951 } 4952 } 4953 } 4954 checkclients()4955 void checkclients() 4956 { 4957 loopv(clients) if(clients[i]->name[0] && clients[i]->online) 4958 { 4959 clientinfo *ci = clients[i]; 4960 if(smode) smode->checkclient(ci); 4961 mutate(smuts, mut->checkclient(ci)); 4962 if(ci->state == CS_ALIVE) 4963 { 4964 // hurt material 4965 if((ci->inmaterial&MATF_FLAGS)&MAT_HURT) 4966 { 4967 if(!ci->lasthurt || gamemillis-ci->lasthurt >= G(hurtdelay)) 4968 { 4969 int flags = HIT_MATERIAL; 4970 if(G(hurtresidual)&WR(BURN)) flags |= HIT_BURN; 4971 if(G(hurtresidual)&WR(BLEED)) flags |= HIT_BLEED; 4972 if(G(hurtresidual)&WR(SHOCK)) flags |= HIT_SHOCK; 4973 dodamage(ci, ci, G(hurtdamage), -1, -1, HIT_NONE, flags, ci->inmaterial); 4974 if(!ci->lasthurt) ci->lasthurt = gamemillis; 4975 else ci->lasthurt += G(hurtdelay); 4976 if(ci->state != CS_ALIVE) continue; 4977 } 4978 } 4979 else if(ci->lasthurt && gamemillis-ci->lasthurt >= G(hurtdelay)) ci->lasthurt = 0; 4980 // burning residual 4981 if(ci->burning(gamemillis, G(burntime))) 4982 { 4983 if(gamemillis-ci->lastrestime[WR_BURN] >= G(burndelay)) 4984 { 4985 clientinfo *co = (clientinfo *)getinfo(ci->lastresowner[WR_BURN]); 4986 dodamage(ci, co ? co : ci, G(burndamage), -1, -1, HIT_NONE, HIT_BURN, 0); 4987 ci->lastrestime[WR_BURN] += G(burndelay); 4988 if(ci->state != CS_ALIVE) continue; 4989 } 4990 } 4991 else if(ci->lastres[WR_BURN]) ci->lastres[WR_BURN] = ci->lastrestime[WR_BURN] = 0; 4992 // bleeding residual 4993 if(ci->bleeding(gamemillis, G(bleedtime))) 4994 { 4995 if(gamemillis-ci->lastrestime[WR_BLEED] >= G(bleeddelay)) 4996 { 4997 clientinfo *co = (clientinfo *)getinfo(ci->lastresowner[WR_BLEED]); 4998 dodamage(ci, co ? co : ci, G(bleeddamage), -1, -1, HIT_NONE, HIT_BLEED, 0); 4999 ci->lastrestime[WR_BLEED] += G(bleeddelay); 5000 if(ci->state != CS_ALIVE) continue; 5001 } 5002 } 5003 else if(ci->lastres[WR_BLEED]) ci->lastres[WR_BLEED] = ci->lastrestime[WR_BLEED] = 0; 5004 // shocking residual 5005 if(ci->shocking(gamemillis, G(shocktime))) 5006 { 5007 if(gamemillis-ci->lastrestime[WR_SHOCK] >= G(shockdelay)) 5008 { 5009 clientinfo *co = (clientinfo *)getinfo(ci->lastresowner[WR_SHOCK]); 5010 dodamage(ci, co ? co : ci, G(shockdamage), -1, -1, HIT_NONE, HIT_SHOCK, 0); 5011 ci->lastrestime[WR_SHOCK] += G(shockdelay); 5012 if(ci->state != CS_ALIVE) continue; 5013 } 5014 } 5015 else if(ci->lastres[WR_SHOCK]) ci->lastres[WR_SHOCK] = ci->lastrestime[WR_SHOCK] = 0; 5016 // regen wear-off 5017 if(m_regen(gamemode, mutators) && AA(ci->actortype, abilities)&(1<<A_A_REGEN)) 5018 { 5019 int total = m_health(gamemode, mutators, ci->actortype), amt = G(regenhealth), 5020 delay = ci->lastregen ? G(regentime) : G(regendelay); 5021 if(smode) smode->regen(ci, total, amt, delay); 5022 if(delay && ci->health != total) 5023 { 5024 int millis = gamemillis-(ci->lastregen ? ci->lastregen : ci->lastpain); 5025 if(millis >= delay) 5026 { 5027 int low = 0; 5028 if(ci->health > total) 5029 { 5030 amt = -G(regendecay); 5031 total = ci->health; 5032 low = m_health(gamemode, mutators, ci->actortype); 5033 } 5034 int heal = clamp(ci->health+amt, low, total), eff = heal-ci->health; 5035 if(eff) 5036 { 5037 ci->health = heal; 5038 ci->lastregen = gamemillis; 5039 ci->lastregenamt = eff; 5040 sendf(-1, 1, "ri4", N_REGEN, ci->clientnum, ci->health, ci->lastregenamt); 5041 } 5042 } 5043 } 5044 } 5045 } 5046 else if(ci->state == CS_WAITING) 5047 { 5048 int nospawn = 0; 5049 if(smode && !smode->canspawn(ci, false)) { nospawn++; } 5050 mutate(smuts, if(!mut->canspawn(ci, false)) { nospawn++; }); 5051 if(!nospawn) 5052 { 5053 if(ci->lastdeath) flushevents(ci, ci->lastdeath + DEATHMILLIS); 5054 cleartimedevents(ci); 5055 ci->state = CS_DEAD; // safety 5056 ci->respawn(gamemillis); 5057 sendspawn(ci); 5058 } 5059 } 5060 if(G(autospectate) && !m_duke(gamemode, mutators) && ci->state == CS_DEAD && ci->lastdeath && gamemillis-ci->lastdeath >= G(autospecdelay)) 5061 spectate(ci, true); 5062 } 5063 } 5064 serverupdate()5065 void serverupdate() 5066 { 5067 loopvrev(connects) if(totalmillis-connects[i]->connectmillis >= G(connecttimeout)) 5068 { 5069 clientinfo *ci = connects[i]; 5070 if(ci->connectauth) 5071 { // auth might have stalled 5072 ci->connectauth = false; 5073 ci->authreq = ci->authname[0] = ci->handle[0] = '\0'; 5074 srvmsgftforce(ci->clientnum, CON_EVENT, "\founable to verify, authority request timed out"); 5075 int disc = auth::allowconnect(ci); 5076 if(disc) disconnect_client(ci->clientnum, disc); 5077 else 5078 { 5079 ci->connectmillis = totalmillis ? totalmillis : 1; // in case it doesn't work 5080 connected(ci); 5081 } 5082 } 5083 else disconnect_client(ci->clientnum, DISC_TIMEOUT); 5084 } 5085 loopvrev(control) if(control[i].flag <= ipinfo::INTERNAL) 5086 { 5087 int timeout = 0; 5088 switch(control[i].type) 5089 { 5090 case ipinfo::ALLOW: timeout = G(allowtimeout); break; 5091 case ipinfo::BAN: timeout = G(bantimeout); break; 5092 case ipinfo::MUTE: timeout = G(mutetimeout); break; 5093 case ipinfo::LIMIT: timeout = G(limittimeout); break; 5094 case ipinfo::EXCEPT: timeout = G(excepttimeout); break; 5095 default: break; 5096 } 5097 if(timeout && totalmillis-control[i].time >= timeout) control.remove(i); 5098 } 5099 if(updatecontrols) 5100 { 5101 loopvrev(clients) 5102 { 5103 uint ip = getclientip(clients[i]->clientnum); 5104 if(ip && !haspriv(clients[i], G(banlock)) && checkipinfo(control, ipinfo::BAN, ip) && !checkipinfo(control, ipinfo::EXCEPT, ip)) 5105 { 5106 disconnect_client(clients[i]->clientnum, DISC_IPBAN); 5107 continue; 5108 } 5109 if(clients[i]->kicked) 5110 { 5111 disconnect_client(clients[i]->clientnum, DISC_KICK); 5112 continue; 5113 } 5114 } 5115 updatecontrols = false; 5116 } 5117 if(numclients()) 5118 { 5119 ifserver(shutdownwait) 5120 { 5121 int waituntil = maxshutdownwait*(gs_playing(gamestate) ? 2000 : 1000); 5122 if(totalmillis >= shutdownwait+waituntil) 5123 { 5124 srvoutf(-3, "waited \fs\fc%s\fS to shutdown, overriding and exiting...", timestr(totalmillis-shutdownwait, 4)); 5125 #ifdef STANDALONE 5126 cleanupserver(); 5127 exit(EXIT_SUCCESS); 5128 #else 5129 quit(); 5130 #endif 5131 return; 5132 } 5133 } 5134 if(gs_waiting(gamestate)) 5135 { 5136 int numwait = 0, numgetmap = 0, numnotready = 0; 5137 loopv(clients) 5138 { 5139 clientinfo *cs = clients[i]; 5140 if(cs->actortype > A_PLAYER) continue; 5141 if(m_play(gamemode) && (!cs->ready || (G(waitforplayers) == 2 && cs->state == CS_SPECTATOR))) numwait++; 5142 if(cs->wantsmap || cs->gettingmap) numgetmap++; 5143 if(!cs->ready) numnotready++; 5144 } 5145 switch(gamestate) 5146 { 5147 case G_S_WAITING: // start check 5148 { 5149 if(!G(waitforplayermaps)) 5150 { 5151 gamewaittime = totalmillis+G(waitforplayertime); 5152 gamestate = G_S_READYING; 5153 sendtick(); 5154 break; 5155 } 5156 if(!gamewaittime) 5157 { 5158 gamewaittime = totalmillis+max(m_play(gamemode) ? G(waitforplayerload) : 1, 1); 5159 sendtick(); 5160 } 5161 if(numnotready && gamewaittime > totalmillis) break; 5162 if(!hasmapdata()) 5163 { 5164 if(mapsending < 0) getmap(NULL, true); 5165 if(mapsending >= 0) 5166 { 5167 srvoutf(4, "\fyplease wait while the server downloads the map.."); 5168 gamewaittime = totalmillis+G(waitforplayermaps); 5169 gamestate = G_S_GETMAP; 5170 sendtick(); 5171 break; 5172 } 5173 gamewaittime = totalmillis+G(waitforplayertime); 5174 gamestate = G_S_READYING; 5175 sendtick(); 5176 break; 5177 } 5178 // fall through 5179 } 5180 case G_S_GETMAP: // waiting for server 5181 { 5182 if(!gamewaittime) 5183 { 5184 gamewaittime = totalmillis+G(waitforplayermaps); 5185 sendtick(); 5186 } 5187 if(!hasmapdata() && mapsending >= 0 && gamewaittime > totalmillis) break; 5188 if(numgetmap && hasmapdata()) 5189 { 5190 srvoutf(4, "\fyplease wait for \fs\fc%d\fS %s to download the map..", numgetmap, numgetmap != 1 ? "players" : "player"); 5191 gamewaittime = totalmillis+G(waitforplayermaps); 5192 gamestate = G_S_SENDMAP; 5193 sendtick(); 5194 break; 5195 } 5196 gamewaittime = totalmillis+G(waitforplayertime); 5197 gamestate = G_S_READYING; 5198 sendtick(); 5199 break; 5200 } 5201 case G_S_SENDMAP: // waiting for players 5202 { 5203 if(!gamewaittime) 5204 { 5205 gamewaittime = totalmillis+G(waitforplayermaps); 5206 sendtick(); 5207 } 5208 if(numgetmap && gamewaittime > totalmillis && hasmapdata()) break; 5209 gamewaittime = totalmillis+G(waitforplayertime); 5210 gamestate = G_S_READYING; 5211 sendtick(); 5212 // fall through 5213 } 5214 case G_S_READYING: // waiting for ready 5215 { 5216 if(!gamewaittime) 5217 { 5218 gamewaittime = totalmillis+G(waitforplayertime); 5219 sendtick(); 5220 } 5221 if(numwait && gamewaittime > totalmillis) break; 5222 if(!hasgameinfo) 5223 { 5224 clientinfo *best = NULL; 5225 loopv(clients) 5226 { 5227 clientinfo *cs = clients[i]; 5228 if(cs->actortype > A_PLAYER || !cs->name[0] || !cs->online || cs->wantsmap || !cs->ready) continue; 5229 cs->updatetimeplayed(); 5230 if(!best || cs->timeplayed > best->timeplayed) best = cs; 5231 } 5232 if(best) 5233 { 5234 mapgameinfo = best->clientnum; 5235 srvoutf(4, "\fyrequesting game information from %s..", colourname(best)); 5236 sendf(best->clientnum, 1, "ri", N_GETGAMEINFO); 5237 gamewaittime = totalmillis+G(waitforplayerinfo); 5238 gamestate = G_S_GAMEINFO; 5239 sendtick(); 5240 break; 5241 } 5242 } 5243 gamestate = G_S_PLAYING; 5244 break; 5245 } 5246 case G_S_GAMEINFO: 5247 { 5248 if(!gamewaittime) 5249 { 5250 gamewaittime = totalmillis+G(waitforplayerinfo); 5251 sendtick(); 5252 } 5253 if(!hasgameinfo && gamewaittime > totalmillis) break; 5254 if(hasgameinfo) srvoutf(4, "\fygame information received, starting.."); 5255 else 5256 { 5257 if(mapgameinfo != -2) 5258 { 5259 int asked = 0; 5260 mapgameinfo = -2; 5261 loopv(clients) 5262 { 5263 clientinfo *cs = clients[i]; 5264 if(cs->actortype > A_PLAYER || !cs->name[0] || !cs->online || cs->wantsmap || !cs->ready || cs->clientnum == mapgameinfo) continue; 5265 sendf(cs->clientnum, 1, "ri", N_GETGAMEINFO); 5266 asked++; 5267 } 5268 if(!asked) srvoutf(4, "\fyno game information response, and nobody to ask, giving up.."); 5269 else 5270 { 5271 srvoutf(4, "\fyno game information response, broadcasting.."); 5272 gamewaittime = totalmillis+G(waitforplayerinfo); 5273 sendtick(); 5274 break; 5275 } 5276 } 5277 else srvoutf(4, "\fyno broadcast game information response, giving up.."); 5278 } 5279 mapgameinfo = -1; 5280 } 5281 default: gamestate = G_S_PLAYING; break; 5282 } 5283 if(gamestate == G_S_PLAYING) 5284 { 5285 gamewaittime = 0; 5286 if(m_team(gamemode, mutators)) doteambalance(true); 5287 if(m_play(gamemode) && !m_bomber(gamemode) && !m_duke(gamemode, mutators)) // they do their own "fight" 5288 sendf(-1, 1, "ri3s", N_ANNOUNCE, S_V_FIGHT, CON_SELF, "match start, fight!"); 5289 sendtick(); 5290 } 5291 } 5292 if(canplay() && !paused) gamemillis += curtime; 5293 if(m_demo(gamemode)) readdemo(); 5294 else if(canplay() && !paused) 5295 { 5296 processevents(); 5297 checkents(); 5298 checklimits(); 5299 checkclients(); 5300 if(smode) smode->update(); 5301 mutate(smuts, mut->update()); 5302 } 5303 if(gs_intermission(gamestate) && gamewaittime <= totalmillis) startintermission(true); // wait then call for next map 5304 if(shouldcheckvotes) checkvotes(); 5305 } 5306 else 5307 { 5308 ifserver(shutdownwait) 5309 { 5310 srvoutf(4, "server empty, shutting down as scheduled"); 5311 #ifdef STANDALONE 5312 cleanupserver(); 5313 exit(EXIT_SUCCESS); 5314 #else 5315 quit(); 5316 #endif 5317 return; 5318 } 5319 if(G(rotatecycle) && clocktime-lastrotatecycle >= G(rotatecycle)*60) cleanup(); 5320 } 5321 aiman::checkai(); 5322 auth::update(); 5323 } 5324 5325 int lastquerysort = 0; querysort(const clientinfo * a,const clientinfo * b)5326 bool querysort(const clientinfo *a, const clientinfo *b) 5327 { 5328 if(a->points > b->points) return true; 5329 if(a->points < b->points) return false; 5330 return strcmp(a->name, b->name) < 0; 5331 } 5332 vector<clientinfo *> queryplayers; 5333 clientconnect(int n,uint ip,bool local)5334 int clientconnect(int n, uint ip, bool local) 5335 { 5336 clientinfo *ci = (clientinfo *)getinfo(n); 5337 ci->clientnum = n; 5338 ci->connectmillis = totalmillis ? totalmillis : 1; 5339 ci->sessionid = (rnd(0x1000000)*((totalmillis%10000)+1))&0xFFFFFF; 5340 ci->local = local; 5341 connects.add(ci); 5342 conoutf("%s peer connection attempt from %s [%d]", ci->local ? "Local" : "Remote", gethostip(ci->clientnum), ci->clientnum); 5343 if(!local && (m_local(gamemode) || servertype <= 0)) return DISC_PRIVATE; 5344 sendservinit(ci); 5345 return DISC_NONE; 5346 } 5347 clientdisconnect(int n,bool local,int reason)5348 void clientdisconnect(int n, bool local, int reason) 5349 { 5350 clientinfo *ci = (clientinfo *)getinfo(n); 5351 bool complete = !numclients(n); 5352 if(local) 5353 { 5354 if(m_demo(gamemode)) enddemoplayback(); 5355 } 5356 if(complete && ci->connected) sendstats(); 5357 if(ci->connected) 5358 { 5359 if(reason != DISC_SHUTDOWN) 5360 { 5361 aiman::removeai(ci, complete); 5362 if(!complete) 5363 { 5364 aiman::poke(); 5365 swapteam(ci, ci->team); 5366 } 5367 savestatsscore(ci); 5368 loopv(clients) if(clients[i] != ci) 5369 { 5370 loopvk(clients[i]->fraglog) if(clients[i]->fraglog[k] == ci->clientnum) 5371 clients[i]->fraglog.remove(k--); 5372 } 5373 if(ci->privilege) auth::setprivilege(ci, -1); 5374 if(smode) smode->leavegame(ci, true); 5375 mutate(smuts, mut->leavegame(ci, true)); 5376 savescore(ci); 5377 } 5378 sendf(-1, 1, "ri3", N_DISCONNECT, n, reason); 5379 ci->connected = false; 5380 if(ci->name[0]) 5381 { 5382 int amt = numclients(ci->clientnum); 5383 relayf(2, "\fo%s has left the game (%s, %d %s)", colourname(ci), reason >= 0 ? disc_reasons[reason] : "normal", amt, amt != 1 ? "players" : "player"); 5384 } 5385 clients.removeobj(ci); 5386 queryplayers.removeobj(ci); 5387 } 5388 else connects.removeobj(ci); 5389 if(complete) 5390 { 5391 cleanup(); 5392 } 5393 else shouldcheckvotes = true; 5394 if(n == mapsending) 5395 { 5396 if(hasmapdata()) mapsending = -1; 5397 else resetmapdata(true); 5398 } 5399 if(n == mapgameinfo) mapgameinfo = -1; 5400 } 5401 queryreply(ucharbuf & req,ucharbuf & p)5402 void queryreply(ucharbuf &req, ucharbuf &p) 5403 { 5404 if(!getint(req)) return; 5405 if(!lastquerysort || totalmillis-lastquerysort >= G(queryinterval)) 5406 { 5407 queryplayers.setsize(0); 5408 loopv(clients) if(clients[i]->clientnum >= 0 && clients[i]->name[0] && clients[i]->actortype == A_PLAYER) queryplayers.add(clients[i]); 5409 queryplayers.sort(querysort); 5410 lastquerysort = totalmillis ? totalmillis : 1; 5411 } 5412 putint(p, queryplayers.length()); 5413 putint(p, 15); // number of attrs following 5414 putint(p, VERSION_GAME); // 1 5415 putint(p, gamemode); // 2 5416 putint(p, mutators); // 3 5417 putint(p, timeremaining); // 4 5418 putint(p, maxslots()); // 5 5419 putint(p, serverpass[0] || G(connectlock) ? MM_PASSWORD : (m_local(gamemode) ? MM_PRIVATE : mastermode)); // 6 5420 putint(p, numgamevars); // 7 5421 putint(p, numgamemods); // 8 5422 putint(p, VERSION_MAJOR); // 9 5423 putint(p, VERSION_MINOR); // 10 5424 putint(p, VERSION_PATCH); // 11 5425 putint(p, versionplatform); // 12 5426 putint(p, versionarch); // 13 5427 putint(p, gamestate); // 14 5428 putint(p, timeleft()); // 15 5429 sendstring(smapname, p); 5430 if(*G(serverdesc)) sendstring(limitstring(G(serverdesc), MAXSDESCLEN+1), p); 5431 else 5432 { 5433 #ifdef STANDALONE 5434 sendstring("", p); 5435 #else 5436 const char *cname = client::getname(); 5437 if(!cname || !cname[0]) cname = ""; 5438 sendstring(cname, p); 5439 #endif 5440 } 5441 sendstring(versionbranch, p); 5442 if(!queryplayers.empty()) 5443 { 5444 loopv(queryplayers) sendstring(colourname(queryplayers[i]), p); 5445 loopv(queryplayers) sendstring(queryplayers[i]->handle, p); 5446 } 5447 sendqueryreply(p); 5448 } 5449 receivefile(int sender,uchar * data,int len)5450 int receivefile(int sender, uchar *data, int len) 5451 { 5452 clientinfo *ci = (clientinfo *)getinfo(sender); 5453 ucharbuf p(data, len); 5454 int type = getint(p), n = getint(p), crc = getint(p); 5455 data += p.length(); 5456 len -= p.length(); 5457 if(type != N_SENDMAPFILE) return -1; 5458 if(n < 0 || n >= SENDMAP_MAX) return -1; 5459 if(ci->clientnum != mapsending) return -1; 5460 if(!len) return n; // zero len is no file 5461 if(mapdata[n]) DELETEP(mapdata[n]); 5462 defformatstring(fname, "backups/tempfile.%s", sendmaptypes[n]); 5463 mapdata[n] = opentempfile(fname, "w+b"); 5464 if(!mapdata[n]) 5465 { 5466 srvmsgf(-1, "failed to open temporary file for map"); 5467 return n; 5468 } 5469 mapdata[n]->write(data, len); 5470 if(n == SENDMAP_MPZ) 5471 { 5472 smapcrc = crcstream(mapdata[n]); 5473 if(crc != smapcrc) srvmsgf(-1, "warning: new crc 0x%.8x doesn't match client 0x%.8x [0x%.8x]", smapcrc, crc, ci->clientcrc); 5474 } 5475 return n; 5476 } 5477 5478 static struct msgfilter 5479 { 5480 uchar msgmask[NUMMSG]; 5481 msgfilterserver::msgfilter5482 msgfilter(int msg, ...) 5483 { 5484 memset(msgmask, 0, sizeof(msgmask)); 5485 va_list msgs; 5486 va_start(msgs, msg); 5487 for(uchar val = 1; msg < NUMMSG; msg = va_arg(msgs, int)) 5488 { 5489 if(msg < 0) val = uchar(-msg); 5490 else msgmask[msg] = val; 5491 } 5492 va_end(msgs); 5493 } 5494 operator []server::msgfilter5495 uchar operator[](int msg) const { return msg >= 0 && msg < NUMMSG ? msgmask[msg] : 0; } 5496 } msgfilter(-1, N_CONNECT, N_SERVERINIT, N_CLIENTINIT, N_WELCOME, N_MAPCHANGE, N_SERVMSG, N_DAMAGE, N_SHOTFX, N_LOADW, N_DIED, N_POINTS, N_SPAWNSTATE, N_ITEMACC, N_ITEMSPAWN, N_TICK, N_DISCONNECT, N_CURRENTPRIV, N_PONG, N_RESUME, N_SCOREAFFIN, N_SCORE, N_ANNOUNCE, N_SENDDEMOLIST, N_SENDDEMO, N_DEMOPLAYBACK, N_SENDMAP, N_REGEN, N_CLIENT, N_AUTHCHAL, N_QUEUEPOS, -2, N_REMIP, N_NEWMAP, N_CLIPBOARD, -3, N_EDITENT, N_EDITLINK, N_EDITVAR, N_EDITF, N_EDITT, N_EDITM, N_FLIP, N_COPY, N_PASTE, N_ROTATE, N_REPLACE, N_DELCUBE, N_EDITVSLOT, N_UNDO, N_REDO, -4, N_POS, N_SPAWN, N_DESTROY, NUMMSG), 5497 connectfilter(-1, N_CONNECT, -2, N_AUTHANS, -3, N_PING, NUMMSG); 5498 checktype(int type,clientinfo * ci)5499 int checktype(int type, clientinfo *ci) 5500 { 5501 if(ci) 5502 { 5503 if(!ci->connected) switch(connectfilter[type]) 5504 { 5505 // allow only before authconnect 5506 case 1: return !ci->connectauth ? type : -1; 5507 // allow only during authconnect 5508 case 2: return ci->connectauth ? type : -1; 5509 // always allow 5510 case 3: return type; 5511 // never allow 5512 default: return -1; 5513 } 5514 if(ci->local) return type; 5515 } 5516 switch(msgfilter[type]) 5517 { 5518 // server-only messages 5519 case 1: return ci ? -1 : type; 5520 // only allowed in coop-edit 5521 case 2: if(m_edit(gamemode) && ci && ci->state == CS_EDITING) break; return -1; 5522 // only allowed in coop-edit, no overflow check 5523 case 3: return m_edit(gamemode) && ci && ci->state == CS_EDITING ? type : -1; 5524 // no overflow check 5525 case 4: return type; 5526 } 5527 if(ci && !haspriv(ci, G(overflowlock)) && ++ci->overflow >= G(overflowsize)) return -2; 5528 return type; 5529 } 5530 5531 struct worldstate 5532 { 5533 int uses, len; 5534 uchar *data; 5535 worldstateserver::worldstate5536 worldstate() : uses(0), len(0), data(NULL) {} 5537 setupserver::worldstate5538 void setup(int n) { len = n; data = new uchar[n]; } cleanupserver::worldstate5539 void cleanup() { DELETEA(data); len = 0; } containsserver::worldstate5540 bool contains(const uchar *p) const { return p >= data && p < &data[len]; } 5541 }; 5542 vector<worldstate> worldstates; 5543 bool reliablemessages = false; 5544 cleanworldstate(ENetPacket * packet)5545 void cleanworldstate(ENetPacket *packet) 5546 { 5547 loopv(worldstates) 5548 { 5549 worldstate &ws = worldstates[i]; 5550 if(!ws.contains(packet->data)) continue; 5551 ws.uses--; 5552 if(ws.uses <= 0) 5553 { 5554 ws.cleanup(); 5555 worldstates.removeunordered(i); 5556 } 5557 break; 5558 } 5559 } 5560 sendpositions(worldstate & ws,ucharbuf & wsbuf)5561 static void sendpositions(worldstate &ws, ucharbuf &wsbuf) 5562 { 5563 if(wsbuf.empty()) return; 5564 int wslen = wsbuf.length(); 5565 recordpacket(0, wsbuf.buf, wslen); 5566 wsbuf.put(wsbuf.buf, wslen); 5567 loopv(clients) 5568 { 5569 clientinfo &ci = *clients[i]; 5570 if(ci.actortype != A_PLAYER) continue; 5571 uchar *data = wsbuf.buf; 5572 int size = wslen; 5573 if(ci.wsdata >= wsbuf.buf) { data = ci.wsdata + ci.wslen; size -= ci.wslen; } 5574 if(size <= 0) continue; 5575 ENetPacket *packet = enet_packet_create(data, size, ENET_PACKET_FLAG_NO_ALLOCATE); 5576 sendpacket(ci.clientnum, 0, packet); 5577 if(packet->referenceCount) { ws.uses++; packet->freeCallback = cleanworldstate; } 5578 else enet_packet_destroy(packet); 5579 } 5580 wsbuf.offset(wsbuf.length()); 5581 } 5582 addposition(worldstate & ws,ucharbuf & wsbuf,int mtu,clientinfo & bi,clientinfo & ci)5583 static inline void addposition(worldstate &ws, ucharbuf &wsbuf, int mtu, clientinfo &bi, clientinfo &ci) 5584 { 5585 if(bi.position.empty()) return; 5586 if(wsbuf.length() + bi.position.length() > mtu) sendpositions(ws, wsbuf); 5587 int offset = wsbuf.length(); 5588 wsbuf.put(bi.position.getbuf(), bi.position.length()); 5589 bi.position.setsize(0); 5590 int len = wsbuf.length() - offset; 5591 if(ci.wsdata < wsbuf.buf) { ci.wsdata = &wsbuf.buf[offset]; ci.wslen = len; } 5592 else ci.wslen += len; 5593 } 5594 sendmessages(worldstate & ws,ucharbuf & wsbuf)5595 static void sendmessages(worldstate &ws, ucharbuf &wsbuf) 5596 { 5597 if(wsbuf.empty()) return; 5598 int wslen = wsbuf.length(); 5599 recordpacket(1, wsbuf.buf, wslen); 5600 wsbuf.put(wsbuf.buf, wslen); 5601 loopv(clients) 5602 { 5603 clientinfo &ci = *clients[i]; 5604 if(ci.actortype != A_PLAYER) continue; 5605 uchar *data = wsbuf.buf; 5606 int size = wslen; 5607 if(ci.wsdata >= wsbuf.buf) { data = ci.wsdata + ci.wslen; size -= ci.wslen; } 5608 if(size <= 0) continue; 5609 ENetPacket *packet = enet_packet_create(data, size, (reliablemessages ? ENET_PACKET_FLAG_RELIABLE : 0) | ENET_PACKET_FLAG_NO_ALLOCATE); 5610 sendpacket(ci.clientnum, 1, packet); 5611 if(packet->referenceCount) { ws.uses++; packet->freeCallback = cleanworldstate; } 5612 else enet_packet_destroy(packet); 5613 } 5614 wsbuf.offset(wsbuf.length()); 5615 } 5616 addmessages(worldstate & ws,ucharbuf & wsbuf,int mtu,clientinfo & bi,clientinfo & ci)5617 static inline void addmessages(worldstate &ws, ucharbuf &wsbuf, int mtu, clientinfo &bi, clientinfo &ci) 5618 { 5619 if(bi.messages.empty()) return; 5620 if(wsbuf.length() + 10 + bi.messages.length() > mtu) sendmessages(ws, wsbuf); 5621 int offset = wsbuf.length(); 5622 putint(wsbuf, N_CLIENT); 5623 putint(wsbuf, bi.clientnum); 5624 putuint(wsbuf, bi.messages.length()); 5625 wsbuf.put(bi.messages.getbuf(), bi.messages.length()); 5626 bi.messages.setsize(0); 5627 int len = wsbuf.length() - offset; 5628 if(ci.wsdata < wsbuf.buf) { ci.wsdata = &wsbuf.buf[offset]; ci.wslen = len; } 5629 else ci.wslen += len; 5630 } 5631 buildworldstate()5632 bool buildworldstate() 5633 { 5634 int wsmax = 0; 5635 loopv(clients) 5636 { 5637 clientinfo &ci = *clients[i]; 5638 ci.overflow = 0; 5639 ci.wsdata = NULL; 5640 wsmax += ci.position.length(); 5641 if(ci.messages.length()) wsmax += 10 + ci.messages.length(); 5642 } 5643 if(wsmax <= 0) 5644 { 5645 reliablemessages = false; 5646 return false; 5647 } 5648 worldstate &ws = worldstates.add(); 5649 ws.setup(2*wsmax); 5650 int mtu = getservermtu() - 100; 5651 if(mtu <= 0) mtu = ws.len; 5652 ucharbuf wsbuf(ws.data, ws.len); 5653 loopv(clients) 5654 { 5655 clientinfo &ci = *clients[i]; 5656 if(ci.actortype != A_PLAYER) continue; 5657 addposition(ws, wsbuf, mtu, ci, ci); 5658 loopvj(ci.bots) addposition(ws, wsbuf, mtu, *ci.bots[j], ci); 5659 } 5660 sendpositions(ws, wsbuf); 5661 loopv(clients) 5662 { 5663 clientinfo &ci = *clients[i]; 5664 if(ci.actortype != A_PLAYER) continue; 5665 addmessages(ws, wsbuf, mtu, ci, ci); 5666 loopvj(ci.bots) addmessages(ws, wsbuf, mtu, *ci.bots[j], ci); 5667 } 5668 sendmessages(ws, wsbuf); 5669 reliablemessages = false; 5670 if(ws.uses) return true; 5671 ws.cleanup(); 5672 worldstates.drop(); 5673 return false; 5674 } 5675 sendpackets(bool force)5676 bool sendpackets(bool force) 5677 { 5678 if(clients.empty() || (!hasnonlocalclients() && !demorecord)) return false; 5679 enet_uint32 millis = enet_time_get()-lastsend; 5680 if(millis<40 && !force) return false; 5681 bool flush = buildworldstate(); 5682 lastsend += millis - (millis%40); 5683 return flush; 5684 } 5685 sendclipboard(clientinfo * ci)5686 void sendclipboard(clientinfo *ci) 5687 { 5688 if(!ci->lastclipboard || !ci->clipboard) return; 5689 bool flushed = false; 5690 loopv(clients) 5691 { 5692 clientinfo &e = *clients[i]; 5693 if(e.clientnum != ci->clientnum && e.needclipboard < ci->lastclipboard) 5694 { 5695 if(!flushed) { flushserver(true); flushed = true; } 5696 sendpacket(e.clientnum, 1, ci->clipboard); 5697 e.needclipboard = ci->lastclipboard; 5698 } 5699 } 5700 } 5701 connected(clientinfo * ci)5702 void connected(clientinfo *ci) 5703 { 5704 if(!m_demo(gamemode) && !numclients() && demonextmatch) setupdemorecord(); 5705 5706 connects.removeobj(ci); 5707 clients.add(ci); 5708 5709 ci->connected = true; 5710 ci->needclipboard = 0; 5711 ci->lasttimeplayed = totalmillis ? totalmillis : 1; 5712 ci->lasttimealive = totalmillis ? totalmillis : 1; 5713 ci->lasttimeactive = totalmillis ? totalmillis : 1; 5714 ci->lasttimewielded = totalmillis ? totalmillis : 1; 5715 loopi(W_MAX) ci->lasttimeloadout[i] = totalmillis ? totalmillis : 1; 5716 5717 if(ci->handle[0]) // kick old logins 5718 { 5719 loopvrev(clients) if(clients[i] != ci && clients[i]->handle[0] && !strcmp(clients[i]->handle, ci->handle)) 5720 disconnect_client(clients[i]->clientnum, DISC_AUTH); 5721 } 5722 sendwelcome(ci); 5723 if(restorescore(ci)) sendresume(ci); 5724 sendinitclient(ci); 5725 int amt = numclients(); 5726 if((ci->privilege&PRIV_TYPE) > PRIV_NONE) 5727 { 5728 if(ci->handle[0]) relayf(2, "\fg%s has joined the game (\fs\fy%s\fS: \fs\fc%s\fS) [%d.%d.%d-%s%d-%s] (%d %s)", colourname(ci), privname(ci->privilege), ci->handle, ci->version.major, ci->version.minor, ci->version.patch, plat_name(ci->version.platform), ci->version.arch, ci->version.branch, amt, amt != 1 ? "players" : "player"); 5729 else relayf(2, "\fg%s has joined the game (\fs\fy%s\fS) [%d.%d.%d-%s%d-%s] (%d %s)", colourname(ci), privname(ci->privilege), ci->version.major, ci->version.minor, ci->version.patch, plat_name(ci->version.platform), ci->version.arch, ci->version.branch, amt, amt != 1 ? "players" : "player"); 5730 } 5731 else relayf(2, "\fg%s has joined the game [%d.%d.%d-%s%d-%s] (%d %s)", colourname(ci), ci->version.major, ci->version.minor, ci->version.patch, plat_name(ci->version.platform), ci->version.arch, ci->version.branch, amt, amt != 1 ? "players" : "player"); 5732 5733 if(m_demo(gamemode)) setupdemoplayback(); 5734 else if(m_edit(gamemode)) 5735 { 5736 ci->ready = true; 5737 aiman::poke(); 5738 } 5739 } 5740 parsepacket(int sender,int chan,packetbuf & p)5741 void parsepacket(int sender, int chan, packetbuf &p) // has to parse exactly each byte of the packet 5742 { 5743 if(sender < 0 || p.packet->flags&ENET_PACKET_FLAG_UNSEQUENCED || chan > 2) return; 5744 char text[MAXTRANS]; 5745 int type = -1, prevtype = -1; 5746 clientinfo *ci = sender >= 0 ? (clientinfo *)getinfo(sender) : NULL; 5747 if(ci && !ci->connected) 5748 { 5749 if(chan == 0) return; 5750 else if(chan != 1) 5751 { 5752 conoutf("\fy[msg error] from: %d, chan: %d while connecting", sender, chan); 5753 disconnect_client(sender, DISC_MSGERR); 5754 return; 5755 } 5756 else while(p.length() < p.maxlen) 5757 { 5758 int curtype = getint(p); 5759 prevtype = type; 5760 switch(type = checktype(curtype, ci)) 5761 { 5762 case N_CONNECT: 5763 { 5764 getstring(text, p); 5765 string namestr = ""; 5766 filterstring(namestr, text, true, true, true, true, MAXNAMELEN); 5767 if(!*namestr) copystring(namestr, "unnamed"); 5768 copystring(ci->name, namestr, MAXNAMELEN+1); 5769 ci->colour = max(getint(p), 0); 5770 ci->model = max(getint(p), 0); 5771 getstring(text, p); 5772 ci->setvanity(text); 5773 int lw = getint(p); 5774 ci->loadweap.shrink(0); 5775 loopk(lw) 5776 { 5777 if(k >= W_LOADOUT) getint(p); 5778 else ci->loadweap.add(getint(p)); 5779 } 5780 int rw = getint(p); 5781 ci->randweap.shrink(0); 5782 loopk(rw) 5783 { 5784 if(k >= W_LOADOUT) getint(p); 5785 else ci->randweap.add(getint(p)); 5786 } 5787 5788 string password = "", authname = ""; 5789 getstring(password, p); 5790 getstring(text, p); 5791 filterstring(authname, text, true, true, true, true, 100); 5792 5793 ci->version.get(p); 5794 5795 int disc = auth::allowconnect(ci, authname, password); 5796 if(disc) 5797 { 5798 disconnect_client(sender, disc); 5799 return; 5800 } 5801 5802 if(!ci->connectauth) connected(ci); 5803 5804 break; 5805 } 5806 5807 case N_AUTHANS: 5808 { 5809 uint id = (uint)getint(p); 5810 getstring(text, p); 5811 if(!auth::answerchallenge(ci, id, text)) auth::authfailed(ci->authreq); 5812 break; 5813 } 5814 5815 case N_PING: 5816 getint(p); 5817 break; 5818 5819 default: 5820 conoutf("\fy[msg error] from: %d, cur: %d, msg: %d, prev: %d", sender, curtype, type, prevtype); 5821 disconnect_client(sender, DISC_MSGERR); 5822 return; 5823 } 5824 } 5825 return; 5826 } 5827 else if(chan == 2) 5828 { 5829 int ret = receivefile(sender, p.buf, p.maxlen); 5830 if(ret == SENDMAP_ALL) 5831 { 5832 clientinfo *cs = (clientinfo *)getinfo(mapsending); 5833 if(hasmapdata()) 5834 { 5835 if(cs && !hasgameinfo) sendf(cs->clientnum, 1, "ri", N_GETGAMEINFO); 5836 mapsending = -1; 5837 sendf(-1, 1, "ri", N_SENDMAP); 5838 loopv(clients) 5839 { 5840 clientinfo *cs = clients[i]; 5841 if(cs->actortype > A_PLAYER || !cs->online || !cs->name[0] || !cs->ready) continue; 5842 if(cs->wantsmap || crclocked(cs, true)) getmap(cs); 5843 } 5844 } 5845 else 5846 { 5847 if(cs) cs->wantsmap = true; 5848 resetmapdata(true); 5849 } 5850 } 5851 return; 5852 } 5853 if(p.packet->flags&ENET_PACKET_FLAG_RELIABLE) reliablemessages = true; 5854 #define QUEUE_MSG { if(ci && (!ci->local || demorecord || hasnonlocalclients())) while(curmsg<p.length()) ci->messages.add(p.buf[curmsg++]); } 5855 #define QUEUE_BUF(body) { \ 5856 if(ci && (!ci->local || demorecord || hasnonlocalclients())) \ 5857 { \ 5858 curmsg = p.length(); \ 5859 { body; } \ 5860 } \ 5861 } 5862 #define QUEUE_INT(n) QUEUE_BUF(putint(ci->messages, n)) 5863 #define QUEUE_UINT(n) QUEUE_BUF(putuint(ci->messages, n)) 5864 #define QUEUE_FLT(n) QUEUE_BUF(putfloat(ci->messages, n)) 5865 #define QUEUE_STR(text) QUEUE_BUF(sendstring(text, ci->messages)) 5866 5867 int curmsg; 5868 while((curmsg = p.length()) < p.maxlen) 5869 { 5870 int curtype = getint(p); 5871 prevtype = type; 5872 switch(type = checktype(curtype, ci)) 5873 { 5874 case N_POS: 5875 { 5876 int lcn = getuint(p); 5877 if(lcn<0) 5878 { 5879 disconnect_client(sender, DISC_CN); 5880 return; 5881 } 5882 5883 bool havecn = true; 5884 clientinfo *cp = (clientinfo *)getinfo(lcn); 5885 if(!hasclient(cp, ci)) havecn = false; 5886 5887 p.get(); 5888 getuint(p); 5889 uint flags = getuint(p); 5890 vec pos, floorpos, vel, falling; 5891 float yaw, pitch, roll; 5892 loopk(3) 5893 { 5894 int n = p.get(); 5895 n |= p.get()<<8; 5896 if(flags&(1<<k)) 5897 { 5898 n |= p.get()<<16; 5899 if(n&0x800000) n |= -1<<24; 5900 } 5901 pos[k] = n/DMF; 5902 } 5903 loopk(3) 5904 { 5905 int n = p.get(); 5906 n |= p.get()<<8; 5907 if(flags&(1<<(k+3))) 5908 { 5909 n |= p.get()<<16; 5910 if(n&0x800000) n |= -1<<24; 5911 } 5912 floorpos[k] = n/DMF; 5913 } 5914 int dir = p.get(); 5915 dir |= p.get()<<8; 5916 yaw = dir%360; 5917 pitch = clamp(dir/360, 0, 180)-90; 5918 roll = clamp(int(p.get()), 0, 180)-90; 5919 int mag = p.get(); 5920 if(flags&(1<<6)) mag |= p.get()<<8; 5921 dir = p.get(); 5922 dir |= p.get()<<8; 5923 vecfromyawpitch(dir%360, clamp(dir/360, 0, 180)-90, 1, 0, vel); 5924 vel.mul(mag/DVELF); 5925 if(flags&(1<<7)) 5926 { 5927 mag = p.get(); 5928 if(flags&(1<<8)) mag |= p.get()<<8; 5929 if(flags&(1<<9)) 5930 { 5931 dir = p.get(); 5932 dir |= p.get()<<8; 5933 vecfromyawpitch(dir%360, clamp(dir/360, 0, 180)-90, 1, 0, falling); 5934 } 5935 else falling = vec(0, 0, -1); 5936 falling.mul(mag/DVELF); 5937 } 5938 else falling = vec(0, 0, 0); 5939 if(havecn) 5940 { 5941 vec oldpos = cp->o; 5942 cp->o = pos; 5943 cp->floorpos = floorpos; 5944 cp->vel = vel; 5945 cp->falling = falling; 5946 cp->yaw = yaw; 5947 cp->pitch = pitch; 5948 cp->roll = roll; 5949 if((!ci->local || demorecord || hasnonlocalclients()) && (cp->state==CS_ALIVE || cp->state==CS_EDITING)) 5950 { 5951 cp->position.setsize(0); 5952 while(curmsg<p.length()) cp->position.add(p.buf[curmsg++]); 5953 } 5954 if(cp->state==CS_ALIVE) 5955 { 5956 if(smode) smode->moved(cp, oldpos, cp->o); 5957 mutate(smuts, mut->moved(cp, oldpos, cp->o)); 5958 } 5959 } 5960 break; 5961 } 5962 5963 case N_SPHY: 5964 { 5965 int lcn = getint(p), idx = getint(p); 5966 clientinfo *cp = (clientinfo *)getinfo(lcn); 5967 bool proceed = hasclient(cp, ci), qmsg = false; 5968 switch(idx) 5969 { 5970 case SPHY_BOOST: case SPHY_DASH: case SPHY_MELEE: case SPHY_KICK: case SPHY_VAULT: case SPHY_GRAB: case SPHY_SKATE: 5971 { 5972 if(!proceed || cp->state != CS_ALIVE) break; 5973 qmsg = true; 5974 break; 5975 } 5976 case SPHY_COOK: 5977 { 5978 int wstate = getint(p), wlen = getint(p), wtime = getint(p); 5979 if(!proceed) break; 5980 if(!cp->isalive(gamemillis) || !isweap(cp->weapselect) || (wstate != W_S_IDLE && wstate != W_S_ZOOM && wstate != W_S_POWER)) 5981 { 5982 if(G(serverdebug)) srvmsgf(cp->clientnum, "sync error: power [%d] failed - unexpected message", cp->weapselect); 5983 break; 5984 } 5985 if(cp->weapstate[cp->weapselect] == W_S_RELOAD && !cp->weapwaited(cp->weapselect, gamemillis)) 5986 { 5987 if(!cp->weapwaited(cp->weapselect, gamemillis, (1<<W_S_RELOAD))) 5988 { 5989 if(!cp->hasweap(cp->weapselect, m_weapon(cp->actortype, gamemode, mutators))) cp->entid[cp->weapselect] = -1; // its gone.. 5990 if(G(serverdebug)) srvmsgf(cp->clientnum, "sync error: power [%d] failed - current state disallows it", cp->weapselect); 5991 sendresume(ci, true); 5992 break; 5993 } 5994 else if(cp->weapload[cp->weapselect] > 0) 5995 { 5996 takeammo(cp, cp->weapselect, cp->weapload[cp->weapselect]); 5997 cp->weapload[cp->weapselect] = -cp->weapload[cp->weapselect]; 5998 sendf(-1, 1, "ri5x", N_RELOAD, cp->clientnum, cp->weapselect, cp->weapload[cp->weapselect], cp->ammo[cp->weapselect], cp->clientnum); 5999 } 6000 else break; 6001 } 6002 cp->setweapstate(cp->weapselect, wstate, wlen, lastmillis, wtime, wstate == W_S_IDLE); 6003 qmsg = true; 6004 break; 6005 } 6006 case SPHY_MATERIAL: 6007 { 6008 int inmaterial = getint(p); 6009 float submerged = getfloat(p); 6010 if(!proceed) break; 6011 int oldmaterial = cp->inmaterial; 6012 cp->inmaterial = inmaterial; 6013 cp->submerged = submerged; 6014 if(cp->state == CS_ALIVE && (cp->inmaterial&MATF_FLAGS)&MAT_DEATH && !((oldmaterial&MATF_FLAGS)&MAT_DEATH)) 6015 { 6016 suicideevent ev; 6017 ev.flags = HIT_MATERIAL; 6018 ev.material = cp->inmaterial; 6019 ev.process(cp); // process death immediately 6020 } 6021 else if((cp->inmaterial&MATF_VOLUME) == MAT_WATER && cp->burning(gamemillis, G(burntime)) && cp->submerged >= G(liquidextinguish)) 6022 { 6023 cp->lastres[WR_BURN] = cp->lastrestime[WR_BURN] = 0; 6024 sendf(-1, 1, "ri3", N_SPHY, cp->clientnum, SPHY_EXTINGUISH); 6025 } 6026 break; // does not get sent to clients 6027 } 6028 default: break; 6029 } 6030 if(qmsg) QUEUE_MSG; 6031 break; 6032 } 6033 6034 case N_EDITMODE: 6035 { 6036 int val = getint(p); 6037 if(!ci || ci->actortype > A_PLAYER) break; 6038 if(!allowstate(ci, val ? ALST_EDIT : ALST_WALK, G(editlock))) 6039 { 6040 if(G(serverdebug)) srvmsgf(ci->clientnum, "sync error: unable to switch state %s - %d [%d, %d]", colourname(ci), ci->state, ci->lastdeath, gamemillis); 6041 spectator(ci); 6042 break; 6043 } 6044 ci->editspawn(gamemode, mutators); 6045 if(val) 6046 { 6047 if(smode) smode->leavegame(ci); 6048 mutate(smuts, mut->leavegame(ci)); 6049 ci->state = CS_EDITING; 6050 ci->events.deletecontents(); 6051 } 6052 else 6053 { 6054 ci->state = CS_ALIVE; 6055 if(smode) smode->entergame(ci); 6056 mutate(smuts, mut->entergame(ci)); 6057 } 6058 QUEUE_MSG; 6059 break; 6060 } 6061 6062 case N_MAPCRC: 6063 { 6064 getstring(text, p); 6065 int crc = getint(p); 6066 if(!ci) break; 6067 copystring(ci->clientmap, text); 6068 ci->clientcrc = crc; 6069 ci->ready = true; 6070 ci->wantsmap = ci->gettingmap = false; 6071 if(!m_edit(gamemode)) 6072 { 6073 if(hasmapdata()) srvoutf(4, "\fy%s has map crc: \fs\fc0x%.8x\fS (server: \fs\fc0x%.8x\fS)", colourname(ci), ci->clientcrc, smapcrc); 6074 else srvoutf(4, "\fy%s has map crc: \fs\fc0x%.8x\fS", colourname(ci), ci->clientcrc); 6075 } 6076 if(crclocked(ci, true)) getmap(ci); 6077 if(ci->isready()) aiman::poke(); 6078 break; 6079 } 6080 6081 case N_TRYSPAWN: 6082 { 6083 int lcn = getint(p); 6084 clientinfo *cp = (clientinfo *)getinfo(lcn); 6085 if(!hasclient(cp, ci)) break; 6086 if(!allowstate(cp, ALST_TRY, m_edit(gamemode) ? G(spawneditlock) : G(spawnlock))) 6087 { 6088 if(G(serverdebug)) srvmsgf(cp->clientnum, "sync error: unable to spawn %s - %d [%d, %d]", colourname(cp), cp->state, cp->lastdeath, gamemillis); 6089 //spectator(cp); 6090 break; 6091 } 6092 int nospawn = 0; 6093 if(smode && !smode->canspawn(cp, true)) { nospawn++; } 6094 mutate(smuts, if(!mut->canspawn(cp, true)) { nospawn++; }); 6095 if(!nospawn) 6096 { 6097 cp->state = CS_DEAD; 6098 waiting(cp, DROP_RESET); 6099 } 6100 break; 6101 } 6102 6103 case N_WSELECT: 6104 { 6105 int lcn = getint(p), id = getint(p), weap = getint(p); 6106 clientinfo *cp = (clientinfo *)getinfo(lcn); 6107 if(!hasclient(cp, ci) || !isweap(weap) || weap >= W_ALL) break; 6108 switchevent *ev = new switchevent; 6109 ev->id = id; 6110 ev->weap = weap; 6111 ev->millis = cp->getmillis(gamemillis, ev->id); 6112 cp->addevent(ev); 6113 break; 6114 } 6115 6116 case N_SPAWN: 6117 { 6118 int lcn = getint(p); 6119 clientinfo *cp = (clientinfo *)getinfo(lcn); 6120 if(!hasclient(cp, ci)) break; 6121 if(!allowstate(cp, ALST_SPAWN)) 6122 { 6123 if(G(serverdebug)) srvmsgf(cp->clientnum, "sync error: unable to spawn %s - %d [%d, %d]", colourname(cp), cp->state, cp->lastdeath, gamemillis); 6124 //spectator(cp); 6125 break; 6126 } 6127 cp->updatetimeplayed(); 6128 cp->state = CS_ALIVE; 6129 if(smode) smode->spawned(cp); 6130 mutate(smuts, mut->spawned(cp);); 6131 QUEUE_BUF({ 6132 putint(ci->messages, N_SPAWN); 6133 putint(ci->messages, cp->clientnum); 6134 sendstate(cp, ci->messages); 6135 }); 6136 break; 6137 } 6138 6139 case N_SUICIDE: 6140 { 6141 int lcn = getint(p), flags = getint(p), material = getint(p); 6142 clientinfo *cp = (clientinfo *)getinfo(lcn); 6143 if(!hasclient(cp, ci)) break; 6144 suicideevent ev; 6145 ev.flags = flags; 6146 cp->inmaterial = ev.material = material; 6147 ev.process(cp); // process death immediately 6148 break; 6149 } 6150 6151 case N_SHOOT: 6152 { 6153 int lcn = getint(p); 6154 clientinfo *cp = (clientinfo *)getinfo(lcn); 6155 bool havecn = hasclient(cp, ci); 6156 shotevent *ev = new shotevent; 6157 ev->id = getint(p); 6158 ev->weap = getint(p); 6159 ev->flags = getint(p); 6160 ev->scale = getint(p); 6161 if(!isweap(ev->weap)) havecn = false; 6162 else 6163 { 6164 ev->scale = clamp(ev->scale, 0, W2(ev->weap, cooktime, WS(ev->flags))); 6165 if(havecn) ev->millis = cp->getmillis(gamemillis, ev->id); 6166 } 6167 loopk(3) ev->from[k] = getint(p); 6168 ev->num = getint(p); 6169 loopj(ev->num) 6170 { 6171 if(p.overread()) break; 6172 if(j >= MAXPARAMS || !havecn) 6173 { 6174 loopk(4) getint(p); 6175 continue; 6176 } 6177 shotmsg &s = ev->shots.add(); 6178 s.id = getint(p); 6179 loopk(3) s.pos[k] = getint(p); 6180 } 6181 if(havecn) 6182 { 6183 int rays = min(W2(ev->weap, rays, WS(ev->flags)), MAXPARAMS); 6184 if(rays > 1 && W2(ev->weap, cooktime, WS(ev->flags))) rays = int(ceilf(rays*ev->scale/float(W2(ev->weap, cooktime, WS(ev->flags))))); 6185 while(ev->shots.length() > rays) ev->shots.remove(rnd(ev->shots.length())); 6186 cp->addevent(ev); 6187 cp->lastshoot = gamemillis; 6188 } 6189 else delete ev; 6190 break; 6191 } 6192 6193 case N_DROP: 6194 { 6195 int lcn = getint(p), id = getint(p), weap = getint(p); 6196 clientinfo *cp = (clientinfo *)getinfo(lcn); 6197 if(!hasclient(cp, ci)) break; 6198 dropevent *ev = new dropevent; 6199 ev->id = id; 6200 ev->weap = weap; 6201 ev->millis = cp->getmillis(gamemillis, ev->id); 6202 cp->events.add(ev); 6203 break; 6204 } 6205 6206 case N_RELOAD: 6207 { 6208 int lcn = getint(p), id = getint(p), weap = getint(p); 6209 clientinfo *cp = (clientinfo *)getinfo(lcn); 6210 if(!hasclient(cp, ci)) break; 6211 reloadevent *ev = new reloadevent; 6212 ev->id = id; 6213 ev->weap = weap; 6214 ev->millis = cp->getmillis(gamemillis, ev->id); 6215 cp->events.add(ev); 6216 break; 6217 } 6218 6219 case N_DESTROY: 6220 { 6221 int lcn = getint(p), millis = getint(p); 6222 clientinfo *cp = (clientinfo *)getinfo(lcn); 6223 bool havecn = hasclient(cp, ci); 6224 destroyevent *ev = new destroyevent; 6225 ev->weap = getint(p); 6226 ev->fromweap = getint(p); 6227 ev->fromflags = getint(p); 6228 ev->flags = getint(p); 6229 if(havecn) ev->millis = cp->getmillis(gamemillis, millis); 6230 ev->id = getint(p); 6231 ev->radial = getint(p); 6232 ev->scale = getint(p); 6233 int hits = getint(p); 6234 loopj(hits) 6235 { 6236 if(p.overread()) break; 6237 static hitset dummy; 6238 hitset &hit = havecn && j < 100 ? ev->hits.add() : dummy; 6239 hit.flags = getint(p); 6240 hit.proj = getint(p); 6241 hit.target = getint(p); 6242 hit.dist = max(getint(p), 0); 6243 loopk(3) hit.dir[k] = getint(p); 6244 loopk(3) hit.vel[k] = getint(p); 6245 } 6246 if(havecn) cp->events.add(ev); 6247 else delete ev; 6248 break; 6249 } 6250 6251 case N_STICKY: 6252 { 6253 int lcn = getint(p), millis = getint(p); 6254 clientinfo *cp = (clientinfo *)getinfo(lcn); 6255 bool havecn = hasclient(cp, ci); 6256 stickyevent *ev = new stickyevent; 6257 ev->weap = getint(p); 6258 ev->flags = getint(p); 6259 if(havecn) ev->millis = cp->getmillis(gamemillis, millis); 6260 ev->id = getint(p); 6261 ev->target = getint(p); 6262 loopk(3) ev->norm[k] = getint(p); 6263 loopk(3) ev->pos[k] = getint(p); 6264 if(havecn) cp->events.add(ev); 6265 else delete ev; 6266 break; 6267 } 6268 6269 case N_ITEMUSE: 6270 { 6271 int lcn = getint(p), id = getint(p), ent = getint(p); 6272 clientinfo *cp = (clientinfo *)getinfo(lcn); 6273 if(!hasclient(cp, ci)) break; 6274 useevent *ev = new useevent; 6275 ev->id = id; 6276 ev->ent = ent; 6277 ev->millis = cp->getmillis(gamemillis, ev->id); 6278 cp->events.add(ev); 6279 break; 6280 } 6281 6282 case N_TRIGGER: 6283 { 6284 int lcn = getint(p), ent = getint(p); 6285 clientinfo *cp = (clientinfo *)getinfo(lcn); 6286 if(!hasclient(cp, ci) || cp->state != CS_ALIVE) break; 6287 if(sents.inrange(ent)) 6288 { 6289 if(sents[ent].type == CHECKPOINT) 6290 { 6291 if(sents[ent].attrs[5] && sents[ent].attrs[5] != triggerid) break; 6292 if(!m_check(sents[ent].attrs[3], sents[ent].attrs[4], gamemode, mutators)) break; 6293 if(!m_race(gamemode) || (m_ra_gauntlet(gamemode, mutators) && cp->team != T_ALPHA)) break; 6294 if(cp->cpnodes.find(ent) >= 0) break; 6295 switch(sents[ent].attrs[6]) 6296 { 6297 case CP_LAST: case CP_FINISH: 6298 { 6299 if(cp->cpmillis) 6300 { 6301 int laptime = gamemillis-cp->cpmillis, total = 0; 6302 if(cp->cptime <= 0 || laptime < cp->cptime) cp->cptime = laptime; 6303 cp->points++; 6304 sendf(-1, 1, "ri6", N_CHECKPOINT, cp->clientnum, ent, laptime, cp->cptime, cp->points); 6305 if(m_team(gamemode, mutators)) 6306 { 6307 if(m_laptime(gamemode, mutators)) 6308 { 6309 score &ts = teamscore(cp->team); 6310 if(!ts.total || ts.total > cp->cptime) 6311 { 6312 total = ts.total = cp->cptime; 6313 sendf(-1, 1, "ri3", N_SCORE, ts.team, ts.total); 6314 } 6315 } 6316 else 6317 { 6318 score &ts = teamscore(cp->team); 6319 total = ++ts.total; 6320 sendf(-1, 1, "ri3", N_SCORE, ts.team, ts.total); 6321 } 6322 if(total && m_ra_gauntlet(gamemode, mutators) && G(racegauntletwinner)) 6323 { 6324 int numt = numteams(gamemode, mutators); 6325 if(curbalance == numt-1) 6326 { 6327 bool found = false; 6328 loopi(numt) 6329 { 6330 int t = i+T_FIRST, s = teamscore(t).total; 6331 if(t != T_OMEGA && (m_laptime(gamemode, mutators) ? s <= total : s >= total)) 6332 { 6333 found = true; 6334 break; 6335 } 6336 } 6337 if(!found) 6338 { 6339 ancmsgft(-1, S_V_NOTIFY, CON_EVENT, "\fybest score has been reached"); 6340 startintermission(); 6341 } 6342 } 6343 } 6344 } 6345 } 6346 else waiting(cp); 6347 cp->cpmillis = 0; 6348 cp->cpnodes.shrink(0); 6349 if(sents[ent].attrs[6] == CP_FINISH) waiting(cp); 6350 break; 6351 } 6352 case CP_START: case CP_RESPAWN: 6353 { 6354 if(cp->cpnodes.find(ent) >= 0) break; 6355 if(sents[ent].attrs[6] == CP_START) 6356 { 6357 if(cp->cpmillis) break; 6358 cp->cpmillis = gamemillis; 6359 } 6360 else if(!cp->cpmillis) 6361 { 6362 waiting(cp); 6363 break; 6364 } 6365 sendf(-1, 1, "ri4", N_CHECKPOINT, cp->clientnum, ent, -1); 6366 cp->cpnodes.add(ent); 6367 } 6368 default: break; 6369 } 6370 } 6371 else if(sents[ent].type == TRIGGER) 6372 { 6373 if(sents[ent].attrs[4] && sents[ent].attrs[4] != triggerid) break; 6374 if(!m_check(sents[ent].attrs[5], sents[ent].attrs[6], gamemode, mutators)) break; 6375 bool commit = false, kin = false; 6376 switch(sents[ent].attrs[1]) 6377 { 6378 case TR_TOGGLE: 6379 { 6380 sents[ent].millis = gamemillis+(triggertime(ent)*2); 6381 sents[ent].spawned = !sents[ent].spawned; 6382 commit = kin = true; 6383 break; 6384 } 6385 case TR_ONCE: if(sents[ent].spawned) break; 6386 case TR_LINK: 6387 { 6388 sents[ent].millis = gamemillis+(triggertime(ent)*2); 6389 kin = true; 6390 if(!sents[ent].spawned) 6391 { 6392 sents[ent].spawned = true; 6393 commit = true; 6394 } 6395 break; 6396 } 6397 case TR_EXIT: 6398 { 6399 if(sents[ent].spawned) break; 6400 sents[ent].spawned = true; 6401 } 6402 } 6403 if(commit) sendf(-1, 1, "ri3x", N_TRIGGER, ent, sents[ent].spawned ? 1 : 0, cp->clientnum); 6404 if(kin) loopvj(sents[ent].kin) if(sents.inrange(sents[ent].kin[j])) 6405 { 6406 if(sents[sents[ent].kin[j]].type == TRIGGER && !m_check(sents[sents[ent].kin[j]].attrs[5], sents[sents[ent].kin[j]].attrs[6], gamemode, mutators)) 6407 continue; 6408 sents[sents[ent].kin[j]].spawned = sents[ent].spawned; 6409 sents[sents[ent].kin[j]].millis = sents[ent].millis; 6410 } 6411 } 6412 } 6413 else if(G(serverdebug)) srvmsgf(cp->clientnum, "sync error: cannot trigger %d - not a trigger", ent); 6414 break; 6415 } 6416 6417 case N_TEXT: 6418 { 6419 int fcn = getint(p), tcn = getint(p), flags = getint(p); 6420 getstring(text, p); 6421 clientinfo *fcp = (clientinfo *)getinfo(fcn); 6422 clientinfo *tcp = (clientinfo *)getinfo(tcn); 6423 if(!hasclient(fcp, ci)) break; 6424 if(!haspriv(fcp, G(messagelock), "send messages on this server")) break; 6425 uint ip = getclientip(fcp->clientnum); 6426 if(ip && checkipinfo(control, ipinfo::MUTE, ip) && !checkipinfo(control, ipinfo::EXCEPT, ip) && !haspriv(fcp, G(mutelock), "send messages while muted")) break; 6427 if(G(floodlock)) 6428 { 6429 int numlines = 0; 6430 loopvrev(fcp->chatmillis) 6431 { 6432 if(totalmillis-fcp->chatmillis[i] <= G(floodtime)) numlines++; 6433 else fcp->chatmillis.remove(i); 6434 } 6435 if(numlines >= G(floodlines)) 6436 { 6437 if((!fcp->warnings[WARN_CHAT][1] || totalmillis-fcp->warnings[WARN_CHAT][1] >= 1000) && !haspriv(fcp, G(floodlock), "send too many messages consecutively")) 6438 { 6439 fcp->warnings[WARN_CHAT][0]++; 6440 fcp->warnings[WARN_CHAT][1] = totalmillis ? totalmillis : 1; 6441 if(ip && G(floodmute) && fcp->warnings[WARN_CHAT][0] >= G(floodmute) && !checkipinfo(control, ipinfo::EXCEPT, ip) && !haspriv(fcp, G(mutelock))) 6442 { 6443 ipinfo &c = control.add(); 6444 c.ip = ip; 6445 c.mask = 0xFFFFFFFF; 6446 c.type = ipinfo::MUTE; 6447 c.flag = ipinfo::INTERNAL; 6448 c.time = totalmillis ? totalmillis : 1; 6449 c.reason = newstring("exceeded the number of allowed flood warnings"); 6450 srvoutf(-3, "\fs\fcmute\fS added on %s: %s", colourname(fcp), c.reason); 6451 } 6452 } 6453 break; 6454 } 6455 fcp->chatmillis.add(totalmillis ? totalmillis : 1); 6456 } 6457 bigstring output; 6458 copystring(output, text, G(messagelength)); 6459 filterstring(text, text, true, true, true, true, G(messagelength)); 6460 if(*(G(censorwords))) filterword(output, G(censorwords)); 6461 if(flags&SAY_TEAM && !m_team(gamemode, mutators)) flags &= ~SAY_TEAM; 6462 sendf(-1, -1, "ri4s", N_TEXT, fcp->clientnum, tcp ? tcp->clientnum : -1, flags, output); // sent to negative chan for recordpacket 6463 if(flags&SAY_WHISPER && tcp) 6464 { 6465 int scn = allowbroadcast(tcp->clientnum) ? tcp->clientnum : tcp->ownernum; 6466 if(allowbroadcast(scn)) sendf(scn, 1, "ri4s", N_TEXT, fcp->clientnum, tcp->clientnum, flags, output); 6467 if(allowbroadcast(fcp->clientnum) && scn != fcp->clientnum) 6468 sendf(fcp->clientnum, 1, "ri4s", N_TEXT, fcp->clientnum, tcp->clientnum, flags, output); 6469 } 6470 else 6471 { 6472 static vector<int> sentto; 6473 sentto.setsize(0); 6474 loopv(clients) 6475 { 6476 clientinfo *t = clients[i]; 6477 if(flags&SAY_TEAM && fcp->team != t->team) continue; 6478 int scn = t->clientnum; 6479 if(!allowbroadcast(scn) && t->ownernum >= 0) 6480 { 6481 if(strncmp(text, "bots", 4)) 6482 { 6483 size_t len = strlen(t->name); 6484 if(!len || strncasecmp(text, t->name, len)) continue; 6485 switch(text[len]) 6486 { 6487 case 0: break; 6488 case ':': case ',': case ';': len++; break; 6489 default: continue; 6490 } 6491 if(text[len] != 0) continue; 6492 } 6493 scn = t->ownernum; 6494 } 6495 if(!allowbroadcast(scn) || sentto.find(scn) >= 0) continue; 6496 sendf(scn, 1, "ri4s", N_TEXT, fcp->clientnum, tcp ? tcp->clientnum : -1, flags, output); 6497 sentto.add(scn); 6498 } 6499 defformatstring(m, "%s", colourname(fcp)); 6500 if(flags&SAY_TEAM) 6501 { 6502 defformatstring(t, " (to team %s)", colourteam(fcp->team)); 6503 concatstring(m, t); 6504 } 6505 if(flags&SAY_ACTION) relayf(0, "\fv* %s %s", m, output); 6506 else relayf(0, "\fw<%s> %s", m, output); 6507 } 6508 break; 6509 } 6510 6511 case N_COMMAND: 6512 { 6513 int lcn = getint(p), nargs = getint(p); 6514 clientinfo *cp = (clientinfo *)getinfo(lcn); 6515 getstring(text, p); 6516 int alen = getint(p); 6517 if(alen < 0 || alen > p.remaining()) break; 6518 char *arg = newstring(alen); 6519 getstring(arg, p, alen+1); 6520 if(hasclient(cp, ci)) parsecommand(cp, nargs, text, arg); 6521 delete[] arg; 6522 break; 6523 } 6524 6525 case N_SETPLAYERINFO: // name colour model checkpoint vanity count <loadweaps> count <randweaps> 6526 { 6527 uint ip = getclientip(ci->clientnum); 6528 if(ci->lastplayerinfo) 6529 { 6530 bool allow = true; 6531 if(!haspriv(ci, G(setinfolock), "change player info on this server")) allow = false; 6532 else if(ip && checkipinfo(control, ipinfo::MUTE, ip) && !checkipinfo(control, ipinfo::EXCEPT, ip) && !haspriv(ci, G(mutelock), "change player info while muted")) allow = false; 6533 else if(totalmillis-ci->lastplayerinfo < G(setinfowait)) allow = false; 6534 if(!allow) 6535 { 6536 getstring(text, p); 6537 loopk(3) getint(p); 6538 getstring(text, p); 6539 int lw = getint(p); 6540 loopk(lw) getint(p); 6541 int rw = getint(p); 6542 loopk(rw) getint(p); 6543 sendinitclientself(ci); 6544 break; 6545 } 6546 } 6547 QUEUE_MSG; 6548 defformatstring(oldname, "%s", colourname(ci)); 6549 getstring(text, p); 6550 string namestr = ""; 6551 filterstring(namestr, text, true, true, true, true, MAXNAMELEN); 6552 if(!*namestr) copystring(namestr, "unnamed"); 6553 if(strcmp(ci->name, namestr)) 6554 { 6555 copystring(ci->name, namestr, MAXNAMELEN+1); 6556 relayf(2, "\fm* %s is now known as %s", oldname, colourname(ci)); 6557 } 6558 ci->colour = max(getint(p), 0); 6559 ci->model = max(getint(p), 0); 6560 ci->checkpointspawn = max(getint(p), 0); 6561 getstring(text, p); 6562 ci->setvanity(text); 6563 ci->loadweap.shrink(0); 6564 int lw = getint(p); 6565 vector<int> lweaps; 6566 loopk(lw) 6567 { 6568 if(k >= W_LOADOUT) getint(p); 6569 else ci->loadweap.add(getint(p)); 6570 } 6571 ci->randweap.shrink(0); 6572 int rw = getint(p); 6573 loopk(rw) 6574 { 6575 if(k >= W_LOADOUT) getint(p); 6576 else ci->randweap.add(getint(p)); 6577 } 6578 ci->lastplayerinfo = totalmillis ? totalmillis : 1; 6579 QUEUE_STR(ci->name); 6580 QUEUE_INT(ci->colour); 6581 QUEUE_INT(ci->model); 6582 QUEUE_INT(ci->checkpointspawn); 6583 QUEUE_STR(ci->vanity); 6584 QUEUE_INT(ci->loadweap.length()); 6585 loopvk(ci->loadweap) QUEUE_INT(ci->loadweap[k]); 6586 QUEUE_INT(ci->randweap.length()); 6587 loopvk(ci->randweap) QUEUE_INT(ci->randweap[k]); 6588 break; 6589 } 6590 6591 case N_SWITCHTEAM: 6592 { 6593 int team = getint(p); 6594 if(!m_team(gamemode, mutators) || ci->actortype >= A_ENEMY || !isteam(gamemode, mutators, team, T_FIRST)) break; 6595 if(team == ci->team) 6596 { 6597 if(ci->swapteam) 6598 { 6599 if(m_swapteam(gamemode, mutators)) 6600 srvoutf(4, "\fy%s no longer wishes to swap to team %s", colourname(ci), colourteam(ci->swapteam)); 6601 ci->swapteam = T_NEUTRAL; 6602 } 6603 break; 6604 } 6605 uint ip = getclientip(ci->clientnum); 6606 if(ip && checkipinfo(control, ipinfo::LIMIT, ip) && !checkipinfo(control, ipinfo::EXCEPT, ip) && !haspriv(ci, G(limitlock), "change teams while limited")) break; 6607 int newteam = requestswap(ci, team); 6608 if(newteam != team || newteam == ci->team) break; 6609 bool reset = true; 6610 if(ci->state == CS_SPECTATOR) 6611 { 6612 if(!allowstate(ci, ALST_TRY, m_edit(gamemode) ? G(spawneditlock) : G(spawnlock))) 6613 { 6614 if(G(serverdebug)) srvmsgf(ci->clientnum, "sync error: unable to spawn %s - %d [%d, %d]", colourname(ci), ci->state, ci->lastdeath, gamemillis); 6615 spectator(ci); 6616 break; 6617 } 6618 if(!spectate(ci, false)) break; 6619 reset = false; 6620 } 6621 setteam(ci, newteam, (reset ? TT_RESET : 0)|TT_INFOSM); 6622 break; 6623 } 6624 6625 case N_MAPVOTE: 6626 { 6627 getstring(text, p); 6628 filterstring(text, text); 6629 const char *s = text; 6630 if(!strncasecmp(s, "maps/", 5) || !strncasecmp(s, "maps\\", 5)) s += 5; 6631 int reqmode = getint(p), reqmuts = getint(p); 6632 vote(s, reqmode, reqmuts, sender); 6633 break; 6634 } 6635 6636 case N_CLEARVOTE: 6637 { 6638 if(ci->mapvote[0]) 6639 { 6640 ci->mapvote[0] = '\0'; 6641 ci->modevote = ci->mutsvote = -1; 6642 sendf(-1, 1, "ri2", N_CLEARVOTE, ci->clientnum); 6643 } 6644 break; 6645 } 6646 6647 case N_GAMEINFO: 6648 { 6649 bool skip = hasgameinfo || (mapgameinfo == -2 ? crclocked(ci) : mapgameinfo != sender); 6650 int n; 6651 while((n = getint(p)) != -1) 6652 { 6653 if(p.overread()) break; 6654 getstring(text, p); 6655 defformatstring(cmdname, "sv_%s", text); 6656 ident *id = idents.access(cmdname); 6657 if(!skip && id && id->flags&IDF_SERVER && id->flags&IDF_WORLD && n == id->type) 6658 { 6659 switch(id->type) 6660 { 6661 case ID_VAR: 6662 { 6663 int ret = getint(p); 6664 if(ret < id->minval || ret > id->maxval) break; 6665 *id->storage.i = ret; 6666 id->changed(); 6667 break; 6668 } 6669 case ID_FVAR: 6670 { 6671 float ret = getfloat(p); 6672 if(ret < id->minvalf || ret > id->maxvalf) break; 6673 *id->storage.f = ret; 6674 id->changed(); 6675 break; 6676 } 6677 case ID_SVAR: 6678 { 6679 getstring(text, p); 6680 delete[] *id->storage.s; 6681 *id->storage.s = newstring(text); 6682 id->changed(); 6683 break; 6684 } 6685 default: break; 6686 } 6687 } 6688 else switch(n) 6689 { 6690 case ID_VAR: getint(p); break; 6691 case ID_FVAR: getfloat(p); break; 6692 case ID_SVAR: getstring(text, p); break; 6693 default: break; 6694 } 6695 } 6696 while((n = getint(p)) != -1) 6697 { 6698 int type = getint(p), numattr = getint(p); 6699 if(p.overread() || type < 0 || type >= MAXENTTYPES || n < 0 || n >= MAXENTS) break; 6700 if(!skip && enttype[type].syncs) 6701 { 6702 while(sents.length() <= n) sents.add(); 6703 sents[n].reset(); 6704 sents[n].type = type; 6705 sents[n].spawned = false; // wait a bit then load 'em up 6706 sents[n].millis = gamemillis; 6707 sents[n].attrs.add(0, clamp(numattr, type >= 0 && type < MAXENTTYPES ? enttype[type].numattrs : 0, MAXENTATTRS)); 6708 loopk(numattr) 6709 { 6710 if(p.overread()) break; 6711 int attr = getint(p); 6712 if(sents[n].attrs.inrange(k)) sents[n].attrs[k] = attr; 6713 } 6714 if(enttype[type].syncpos) loopj(3) 6715 { 6716 if(p.overread()) break; 6717 sents[n].o[j] = getint(p)/DMF; 6718 } 6719 if(enttype[type].synckin) 6720 { 6721 int numkin = getint(p); 6722 sents[n].kin.add(0, clamp(numkin, 0, MAXENTKIN)); 6723 loopk(numkin) 6724 { 6725 if(p.overread()) break; 6726 int kin = getint(p); 6727 if(k < MAXENTKIN && sents[n].kin.inrange(k)) sents[n].kin[k] = kin; 6728 } 6729 } 6730 } 6731 else 6732 { 6733 loopk(numattr) { if(p.overread()) break; getint(p); } 6734 if(enttype[type].syncpos) loopj(3) { if(p.overread()) break; getint(p); } 6735 if(enttype[type].synckin) 6736 { 6737 int numkin = getint(p); 6738 loopk(numkin) { if(p.overread()) break; getint(p); } 6739 } 6740 } 6741 } 6742 if(!skip) setupgameinfo(); 6743 break; 6744 } 6745 6746 case N_SCORE: 6747 getint(p); 6748 getint(p); 6749 QUEUE_MSG; 6750 break; 6751 6752 case N_INFOAFFIN: 6753 getint(p); 6754 getint(p); 6755 getint(p); 6756 getint(p); 6757 QUEUE_MSG; 6758 break; 6759 6760 case N_SETUPAFFIN: 6761 if(smode==&defendmode) defendmode.parseaffinity(p); 6762 break; 6763 6764 case N_MOVEAFFIN: 6765 { 6766 int cn = getint(p), id = getint(p); 6767 vec o, inertia; 6768 loopi(3) o[i] = getint(p)/DMF; 6769 loopi(3) inertia[i] = getint(p)/DMF; 6770 clientinfo *cp = (clientinfo *)getinfo(cn); 6771 if(!cp || !hasclient(cp, ci)) break; 6772 if(smode==&capturemode) capturemode.moveaffinity(cp, cn, id, o, inertia); 6773 else if(smode==&bombermode) bombermode.moveaffinity(cp, cn, id, o, inertia); 6774 break; 6775 } 6776 6777 case N_TAKEAFFIN: 6778 { 6779 int lcn = getint(p), flag = getint(p); 6780 clientinfo *cp = (clientinfo *)getinfo(lcn); 6781 if(!hasclient(cp, ci) || cp->state == CS_SPECTATOR) break; 6782 if(smode==&capturemode) capturemode.takeaffinity(cp, flag); 6783 else if(smode==&bombermode) bombermode.takeaffinity(cp, flag); 6784 break; 6785 } 6786 6787 case N_RESETAFFIN: 6788 { 6789 int flag = getint(p); 6790 if(!ci) break; 6791 if(smode==&capturemode) capturemode.resetaffinity(ci, flag); 6792 else if(smode==&bombermode) bombermode.resetaffinity(ci, flag); 6793 break; 6794 } 6795 6796 case N_DROPAFFIN: 6797 { 6798 int lcn = getint(p), otc = getint(p); 6799 vec droploc, inertia; 6800 loopk(3) droploc[k] = getint(p)/DMF; 6801 loopk(3) inertia[k] = getint(p)/DMF; 6802 clientinfo *cp = (clientinfo *)getinfo(lcn); 6803 if(!hasclient(cp, ci) || cp->state == CS_SPECTATOR) break; 6804 if(smode==&capturemode) capturemode.dropaffinity(cp, droploc, inertia, -1); 6805 else if(smode==&bombermode) bombermode.dropaffinity(cp, droploc, inertia, otc); 6806 break; 6807 } 6808 6809 case N_INITAFFIN: 6810 { 6811 if(smode==&capturemode) capturemode.parseaffinity(p); 6812 else if(smode==&bombermode) bombermode.parseaffinity(p); 6813 break; 6814 } 6815 6816 case N_PING: 6817 sendf(sender, 1, "i2", N_PONG, getint(p)); 6818 break; 6819 6820 case N_CLIENTPING: 6821 { 6822 int ping = getint(p); 6823 if(ci) 6824 { 6825 ci->ping = ping; 6826 loopv(clients) if(clients[i]->ownernum == ci->clientnum) clients[i]->ping = ping; 6827 } 6828 QUEUE_MSG; 6829 break; 6830 } 6831 6832 case N_MASTERMODE: 6833 { 6834 int mm = getint(p); 6835 if(haspriv(ci, G(masterlock), "change mastermode") && mm >= MM_OPEN && mm <= MM_PRIVATE) 6836 { 6837 if(haspriv(ci, PRIV_ADMINISTRATOR) || (mastermask()&(1<<mm))) 6838 { 6839 mastermode = mm; 6840 resetallows(); 6841 if(mastermode >= MM_PRIVATE) loopv(clients) 6842 { 6843 ipinfo &allow = control.add(); 6844 allow.ip = getclientip(clients[i]->clientnum); 6845 allow.mask = 0xFFFFFFFF; 6846 allow.type = ipinfo::ALLOW; 6847 allow.time = totalmillis ? totalmillis : 1; 6848 allow.reason = newstring("mastermode set private"); 6849 } 6850 sendf(-1, 1, "i3", N_MASTERMODE, ci->clientnum, mastermode); 6851 //srvoutf(-3, "\fymastermode is now \fs\fc%d\fS (\fs\fc%s\fS)", mastermode, mastermodename(mastermode)); 6852 } 6853 else srvmsgft(ci->clientnum, CON_EVENT, "\fothe \fs\fcmastermode\fS of \fs\fc%d\fS (\fs\fc%s\fS) is disabled on this server", mm, mastermodename(mm)); 6854 } 6855 break; 6856 } 6857 6858 case N_CLRCONTROL: 6859 { 6860 int value = getint(p); 6861 #define CONTROLSWITCH(x,y) \ 6862 case x: \ 6863 { \ 6864 if(haspriv(ci, G(y##lock), "clear " #y "s")) \ 6865 { \ 6866 reset##y##s(); \ 6867 srvoutf(-3, "%s cleared existing \fs\fc" #y "s\fS", colourname(ci)); \ 6868 } \ 6869 break; \ 6870 } 6871 6872 switch(value) 6873 { 6874 CONTROLSWITCH(ipinfo::ALLOW, allow); 6875 CONTROLSWITCH(ipinfo::BAN, ban); 6876 CONTROLSWITCH(ipinfo::MUTE, mute); 6877 CONTROLSWITCH(ipinfo::LIMIT, limit); 6878 CONTROLSWITCH(ipinfo::EXCEPT, except); 6879 default: break; 6880 } 6881 #undef CONTROLSWITCH 6882 break; 6883 } 6884 6885 case N_ADDCONTROL: 6886 { 6887 int m = getint(p), value = getint(p); 6888 getstring(text, p); 6889 #define CONTROLSWITCH(x,y) \ 6890 case x: \ 6891 { \ 6892 if(haspriv(ci, G(y##lock), #y " players") && m >= 0) \ 6893 { \ 6894 clientinfo *cp = (clientinfo *)getinfo(m); \ 6895 if(!cp || cp->ownernum >= 0 || (value != ipinfo::EXCEPT && !cmppriv(ci, cp, #y))) break; \ 6896 uint ip = getclientip(cp->clientnum); \ 6897 if(!ip) break; \ 6898 if(checkipinfo(control, ipinfo::EXCEPT, ip)) \ 6899 { \ 6900 if(!haspriv(ci, PRIV_ADMINISTRATOR, #y " protected players")) break; \ 6901 else if(value >= ipinfo::BAN) loopvrev(control) \ 6902 if(control[i].type == ipinfo::EXCEPT && (ip & control[i].mask) == control[i].ip) \ 6903 control.remove(i); \ 6904 } \ 6905 string name; \ 6906 copystring(name, colourname(ci)); \ 6907 if(value >= 0) \ 6908 { \ 6909 ipinfo &c = control.add(); \ 6910 c.ip = ip; \ 6911 c.mask = 0xFFFFFFFF; \ 6912 c.type = value; \ 6913 c.time = totalmillis ? totalmillis : 1; \ 6914 c.reason = newstring(text); \ 6915 if(text[0]) srvoutf(-3, "%s added \fs\fc" #y "\fS on %s: %s", name, colourname(cp), text); \ 6916 else srvoutf(-3, "%s added \fs\fc" #y "\fS on %s", name, colourname(cp)); \ 6917 if(value == ipinfo::BAN) updatecontrols = true; \ 6918 else if(value == ipinfo::LIMIT) cp->swapteam = 0; \ 6919 } \ 6920 else \ 6921 { \ 6922 if(text[0]) srvoutf(-3, "%s \fs\fckicked\fS %s: %s", name, colourname(cp), text); \ 6923 else srvoutf(-3, "%s \fs\fckicked\fS %s", name, colourname(cp)); \ 6924 cp->kicked = updatecontrols = true; \ 6925 } \ 6926 } \ 6927 break; \ 6928 } 6929 switch(value) 6930 { 6931 CONTROLSWITCH(-1, kick); 6932 CONTROLSWITCH(ipinfo::ALLOW, allow); 6933 CONTROLSWITCH(ipinfo::BAN, ban); 6934 CONTROLSWITCH(ipinfo::MUTE, mute); 6935 CONTROLSWITCH(ipinfo::LIMIT, limit); 6936 CONTROLSWITCH(ipinfo::EXCEPT, except); 6937 default: break; 6938 } 6939 #undef CONTROLSWITCH 6940 break; 6941 } 6942 6943 case N_SPECTATOR: 6944 { 6945 int sn = getint(p), val = getint(p); 6946 clientinfo *cp = (clientinfo *)getinfo(sn); 6947 if(!cp || cp->actortype > A_PLAYER || (val ? cp->state == CS_SPECTATOR : cp->state != CS_SPECTATOR)) 6948 { 6949 if(G(serverdebug)) srvmsgf(ci->clientnum, "sync error: unable to modify spectator %s - %d [%d, %d] - invalid", colourname(cp), cp->state, cp->lastdeath, gamemillis); 6950 break; 6951 } 6952 if(sn != sender ? !haspriv(ci, max(m_edit(gamemode) ? G(spawneditlock) : G(spawnlock), G(speclock)), "control other players") : (!haspriv(ci, max(m_edit(gamemode) ? G(spawneditlock) : G(spawnlock), G(speclock))) && !allowstate(cp, val ? ALST_SPEC : ALST_TRY, m_edit(gamemode) ? G(spawneditlock) : G(spawnlock)))) 6953 { 6954 if(G(serverdebug)) srvmsgf(ci->clientnum, "sync error: unable to modify spectator %s - %d [%d, %d] - restricted", colourname(cp), cp->state, cp->lastdeath, gamemillis); 6955 break; 6956 } 6957 bool spec = val != 0, quarantine = cp != ci && val == 2, wasq = cp->quarantine; 6958 if(quarantine && (ci->privilege&PRIV_TYPE) <= (cp->privilege&PRIV_TYPE)) 6959 { 6960 srvmsgf(ci->clientnum, "\fraccess denied, you may not quarantine higher or equally privileged player %s", colourname(cp)); 6961 break; 6962 } 6963 if(!spectate(cp, spec, quarantine)) 6964 { 6965 if(G(serverdebug)) srvmsgf(ci->clientnum, "sync error: unable to modify spectator %s - %d [%d, %d] - failed", colourname(cp), cp->state, cp->lastdeath, gamemillis); 6966 break; 6967 } 6968 if(quarantine && cp->quarantine) 6969 { 6970 defformatstring(name, "%s", colourname(ci)); 6971 srvoutf(-3, "%s \fs\fcquarantined\fS %s", name, colourname(cp)); 6972 } 6973 else if(wasq && !cp->quarantine) 6974 { 6975 defformatstring(name, "%s", colourname(ci)); 6976 srvoutf(-3, "%s \fs\fcreleased\fS %s from \fs\fcquarantine\fS", name, colourname(cp)); 6977 } 6978 break; 6979 } 6980 6981 case N_SETTEAM: 6982 { 6983 int who = getint(p), team = getint(p); 6984 clientinfo *cp = (clientinfo *)getinfo(who); 6985 if(!cp || !m_team(gamemode, mutators) || m_local(gamemode) || cp->actortype >= A_ENEMY) break; 6986 if(who < 0 || who >= getnumclients() || !haspriv(ci, G(teamlock), "change the team of others")) break; 6987 if(cp->state == CS_SPECTATOR || !allowteam(cp, team, T_FIRST, false)) break; 6988 setteam(cp, team, TT_RESETX); 6989 break; 6990 } 6991 6992 case N_RECORDDEMO: 6993 { 6994 int val = getint(p); 6995 if(!haspriv(ci, G(demolock), "record demos")) break; 6996 setdemorecord(val != 0, true); 6997 break; 6998 } 6999 7000 case N_STOPDEMO: 7001 { 7002 if(!haspriv(ci, G(demolock), "stop demos")) break; 7003 if(m_demo(gamemode)) enddemoplayback(); 7004 else checkdemorecord(!gs_playing(gamestate)); 7005 break; 7006 } 7007 7008 case N_CLEARDEMOS: 7009 { 7010 int demo = getint(p); 7011 if(!haspriv(ci, G(demolock), "clear demos")) break; 7012 cleardemos(demo); 7013 break; 7014 } 7015 7016 case N_LISTDEMOS: 7017 listdemos(sender); 7018 break; 7019 7020 case N_GETDEMO: 7021 { 7022 int n = getint(p); 7023 int dni = getint(p); 7024 senddemo(sender, n, dni); 7025 break; 7026 } 7027 7028 case N_EDITENT: 7029 { 7030 int n = getint(p), oldtype = NOTUSED, newtype = NOTUSED; 7031 ivec o(0, 0, 0); 7032 bool tweaked = false, inrange = n < MAXENTS; 7033 loopk(3) o[k] = getint(p); 7034 if(p.overread()) break; 7035 if(sents.inrange(n)) oldtype = sents[n].type; 7036 else if(inrange) while(sents.length() <= n) sents.add(); 7037 if((newtype = getint(p)) != oldtype && inrange) 7038 { 7039 sents[n].type = newtype; 7040 tweaked = true; 7041 } 7042 int numattrs = getint(p), realattrs = min(max(5, numattrs), MAXENTATTRS); 7043 if(inrange) while(sents[n].attrs.length() < realattrs) sents[n].attrs.add(0); 7044 loopk(numattrs) 7045 { 7046 int attr = getint(p); 7047 if(p.overread()) break; 7048 if(inrange && k < MAXENTATTRS) sents[n].attrs[k] = attr; 7049 } 7050 if(inrange) 7051 { 7052 hasgameinfo = true; 7053 sents[n].o = vec(o).div(DMF); 7054 packetbuf q(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); 7055 uchar s[MAXTRANS]; 7056 ucharbuf r(s, MAXTRANS); 7057 putint(q, N_CLIENT); 7058 putint(q, ci->clientnum); 7059 putint(r, N_EDITENT); 7060 putint(r, n); 7061 putint(r, o.x); 7062 putint(r, o.y); 7063 putint(r, o.z); 7064 putint(r, sents[n].type); 7065 putint(r, sents[n].attrs.length()); 7066 loopvk(sents[n].attrs) putint(r, sents[n].attrs[k]); 7067 putuint(q, r.length()); 7068 q.put(r.getbuf(), r.length()); 7069 sendpacket(-1, 1, q.finalize(), ci->clientnum); 7070 if(tweaked) 7071 { 7072 if(enttype[sents[n].type].usetype == EU_ITEM) setspawn(n, true, true, true); 7073 if(oldtype == PLAYERSTART || sents[n].type == PLAYERSTART) setupspawns(true); 7074 if(oldtype == TRIGGER || sents[n].type == TRIGGER) setuptriggers(true); 7075 } 7076 } 7077 break; 7078 } 7079 7080 case N_EDITVAR: 7081 { 7082 int t = getint(p); 7083 getstring(text, p); 7084 if(!ci || ci->state != CS_EDITING) 7085 { 7086 switch(t) 7087 { 7088 case ID_VAR: getint(p); break; 7089 case ID_FVAR: getfloat(p); break; 7090 case ID_SVAR: case ID_ALIAS: 7091 { 7092 int vlen = getint(p); 7093 if(vlen < 0 || vlen > p.remaining()) break; 7094 getstring(text, p, vlen+1); 7095 break; 7096 } 7097 default: break; 7098 } 7099 break; 7100 } 7101 QUEUE_INT(N_EDITVAR); 7102 QUEUE_INT(t); 7103 QUEUE_STR(text); 7104 switch(t) 7105 { 7106 case ID_VAR: 7107 { 7108 int val = getint(p); 7109 relayf(3, "\fy%s set worldvar %s to %d", colourname(ci), text, val); 7110 QUEUE_INT(val); 7111 break; 7112 } 7113 case ID_FVAR: 7114 { 7115 float val = getfloat(p); 7116 relayf(3, "\fy%s set worldvar %s to %s", colourname(ci), text, floatstr(val)); 7117 QUEUE_FLT(val); 7118 break; 7119 } 7120 case ID_SVAR: 7121 case ID_ALIAS: 7122 { 7123 int vlen = getint(p); 7124 if(vlen < 0 || vlen > p.remaining()) break; 7125 char *val = newstring(vlen); 7126 getstring(val, p, vlen+1); 7127 relayf(3, "\fy%s set world%s %s to %s", colourname(ci), t == ID_ALIAS ? "alias" : "var", text, val); 7128 QUEUE_INT(vlen); 7129 QUEUE_STR(val); 7130 delete[] val; 7131 break; 7132 } 7133 default: break; 7134 } 7135 break; 7136 } 7137 7138 case N_GETMAP: 7139 { 7140 ci->ready = true; 7141 getmap(ci); 7142 break; 7143 } 7144 7145 case N_NEWMAP: 7146 { 7147 int size = getint(p); 7148 getstring(text, p); 7149 if(ci->state != CS_EDITING) break; 7150 QUEUE_INT(N_NEWMAP); 7151 QUEUE_INT(size); 7152 if(size >= 0) 7153 { 7154 if(*text) formatstring(smapname, strstr(text, "maps/")==text || strstr(text, "maps\\")==text ? "%s" : "maps/%s", text); 7155 else copystring(smapname, "maps/untitled"); 7156 sents.shrink(0); 7157 hasgameinfo = true; 7158 mapgameinfo = -1; 7159 if(smode) smode->reset(); 7160 mutate(smuts, mut->reset()); 7161 QUEUE_STR(smapname); 7162 } 7163 else QUEUE_STR(text); 7164 break; 7165 } 7166 7167 case N_SETPRIV: 7168 { 7169 int val = getint(p); 7170 getstring(text, p); 7171 if(val != 0) 7172 { 7173 if(text[0]) 7174 { 7175 if(!adminpass[0]) srvmsgft(ci->clientnum, CON_EVENT, "\fraccess denied, no administrator password set"); 7176 else if(!checkpassword(ci, adminpass, text)) srvmsgft(ci->clientnum, CON_EVENT, "\fraccess denied, invalid administrator password"); 7177 else auth::setprivilege(ci, 1, PRIV_ADMINISTRATOR|PRIV_LOCAL); 7178 } 7179 else if((ci->privilege&PRIV_TYPE) < PRIV_ELEVATED) 7180 { 7181 bool fail = false; 7182 if(!(mastermask()&MM_AUTOAPPROVE)) 7183 { 7184 srvmsgft(ci->clientnum, CON_EVENT, "\fraccess denied, you need a \fs\fcpassword/account\fS to \fs\fcelevate privileges\fS"); 7185 fail = true; 7186 } 7187 else loopv(clients) if(ci != clients[i] && (clients[i]->privilege&PRIV_TYPE) >= PRIV_ELEVATED) 7188 { 7189 srvmsgft(ci->clientnum, CON_EVENT, "\fraccess denied, there is already another player with elevated privileges"); 7190 fail = true; 7191 break; 7192 } 7193 if(!fail) auth::setprivilege(ci, 1, PRIV_ELEVATED|PRIV_LOCAL); 7194 } 7195 } 7196 else auth::setprivilege(ci, 0); 7197 break; // don't broadcast the password 7198 } 7199 7200 case N_AUTHTRY: 7201 { 7202 getstring(text, p); 7203 string authname = ""; 7204 filterstring(authname, text, true, true, true, true, 100); 7205 auth::tryauth(ci, authname); 7206 break; 7207 } 7208 7209 case N_AUTHANS: 7210 { 7211 uint id = (uint)getint(p); 7212 getstring(text, p); 7213 auth::answerchallenge(ci, id, text); 7214 break; 7215 } 7216 7217 case N_COPY: 7218 ci->cleanclipboard(); 7219 ci->lastclipboard = totalmillis ? totalmillis : 1; 7220 goto genericmsg; 7221 7222 case N_PASTE: 7223 if(ci->state == CS_EDITING) sendclipboard(ci); 7224 goto genericmsg; 7225 7226 case N_CLIPBOARD: 7227 { 7228 int unpacklen = getint(p), packlen = getint(p); 7229 ci->cleanclipboard(); 7230 ci->lastclipboard = totalmillis ? totalmillis : 1; 7231 if(ci->state != CS_EDITING) 7232 { 7233 if(packlen > 0) p.subbuf(packlen); 7234 break; 7235 } 7236 if(packlen <= 0 || packlen > (1<<16) || unpacklen <= 0) 7237 { 7238 if(packlen > 0) p.subbuf(packlen); 7239 packlen = unpacklen = 0; 7240 } 7241 packetbuf q(32 + packlen, ENET_PACKET_FLAG_RELIABLE); 7242 putint(q, N_CLIPBOARD); 7243 putint(q, ci->clientnum); 7244 putint(q, unpacklen); 7245 putint(q, packlen); 7246 if(packlen > 0) p.get(q.subbuf(packlen).buf, packlen); 7247 ci->clipboard = q.finalize(); 7248 ci->clipboard->referenceCount++; 7249 break; 7250 } 7251 7252 case N_EDITT: 7253 case N_REPLACE: 7254 case N_EDITVSLOT: 7255 { 7256 int size = msgsizelookup(type); 7257 if(size<=0) { disconnect_client(sender, DISC_MSGERR); return; } 7258 loopi(size-1) getint(p); 7259 if(p.remaining() < 2) { disconnect_client(sender, DISC_MSGERR); return; } 7260 int extra = lilswap(*(const ushort *)p.pad(2)); 7261 if(p.remaining() < extra) { disconnect_client(sender, DISC_MSGERR); return; } 7262 p.pad(extra); 7263 if(ci && ci->state!=CS_SPECTATOR) QUEUE_MSG; 7264 break; 7265 } 7266 7267 case N_UNDO: 7268 case N_REDO: 7269 { 7270 int unpacklen = getint(p), packlen = getint(p); 7271 if(!ci || ci->state==CS_SPECTATOR || packlen <= 0 || packlen > (1<<16) || unpacklen <= 0) 7272 { 7273 if(packlen > 0) p.subbuf(packlen); 7274 break; 7275 } 7276 if(p.remaining() < packlen) { disconnect_client(sender, DISC_MSGERR); return; } 7277 uchar s[MAXTRANS]; 7278 ucharbuf q(s, MAXTRANS); 7279 putint(q, type); 7280 putint(q, ci->clientnum); 7281 putint(q, unpacklen); 7282 putint(q, packlen); 7283 if(packlen > 0) p.get(q.subbuf(packlen).buf, packlen); 7284 ci->messages.put(q.buf, q.length()); 7285 curmsg += q.length(); 7286 break; 7287 } 7288 7289 case N_ADDPRIV: 7290 { 7291 int sn = getint(p), priv = getint(p); 7292 clientinfo *cp = (clientinfo *)getinfo(sn); 7293 if(!cp) 7294 { 7295 srvmsgft(ci->clientnum, CON_EVENT, "\frthat client does not exist"); 7296 break; 7297 } 7298 if((priv != -1) && (priv < PRIV_SUPPORTER || priv > PRIV_ADMINISTRATOR || cp->actortype != A_PLAYER)) 7299 { 7300 srvmsgft(ci->clientnum, CON_EVENT, "\fryou may not add that privilege"); 7301 break; 7302 } 7303 if(priv == -1 && (ci->privilege&PRIV_TYPE) <= (cp->privilege&PRIV_TYPE) && (ci->privilege&PRIV_TYPE) < PRIV_ADMINISTRATOR) 7304 { 7305 srvmsgft(ci->clientnum, CON_EVENT, "\fryou must be a \fs\fc%s\fS to reset that client's privileges", privname((cp->privilege & PRIV_TYPE) + 1)); 7306 break; 7307 } 7308 if(!((ci->privilege&PRIV_TYPE) >= PRIV_ADMINISTRATOR) && !haspriv(ci, priv, "add that privilege")) break; 7309 if(priv == -1) 7310 { 7311 if(cp->oldprivilege == -1) 7312 { 7313 srvmsgft(ci->clientnum, CON_EVENT, "\fr%s does not have any added privilege", colourname(cp)); 7314 break; 7315 } 7316 else 7317 { 7318 auth::setprivilege(cp, 1, cp->oldprivilege, false, ci); 7319 cp->oldprivilege = -1; 7320 break; 7321 } 7322 } 7323 if(priv <= (cp->privilege&PRIV_TYPE)) 7324 { 7325 srvmsgft(ci->clientnum, CON_EVENT, "\fr%s is already elevated to \fs\fc%s\fS", colourname(cp), privname(cp->privilege)); 7326 break; 7327 } 7328 if(cp->oldprivilege == -1) cp->oldprivilege = cp->privilege; 7329 auth::setprivilege(cp, 1, priv|PRIV_LOCAL, false, ci); 7330 break; 7331 } 7332 7333 case -1: 7334 conoutf("\fy[msg error] from: %d, cur: %d, msg: %d, prev: %d", sender, curtype, type, prevtype); 7335 disconnect_client(sender, DISC_MSGERR); 7336 return; 7337 7338 case -2: 7339 disconnect_client(sender, DISC_OVERFLOW); 7340 return; 7341 7342 default: genericmsg: 7343 { 7344 int size = msgsizelookup(type); 7345 if(size<=0) 7346 { 7347 conoutf("\fy[msg error] from: %d, cur: %d, msg: %d, prev: %d", sender, curtype, type, prevtype); 7348 disconnect_client(sender, DISC_MSGERR); 7349 return; 7350 } 7351 loopi(size-1) getint(p); 7352 if(ci) QUEUE_MSG; 7353 break; 7354 } 7355 } 7356 if(verbose > 5) conoutf("\fy[server] from: %d, cur: %d, msg: %d, prev: %d", sender, curtype, type, prevtype); 7357 } 7358 } 7359 serveroption(char * arg)7360 bool serveroption(char *arg) 7361 { 7362 if(arg[0]=='-' && arg[1]=='s') switch(arg[2]) 7363 { 7364 case 'P': setsvar("adminpass", &arg[3]); return true; 7365 case 'k': setsvar("serverpass", &arg[3]); return true; 7366 default: break; 7367 } 7368 return false; 7369 } 7370 }; 7371 #undef GAMESERVER 7372