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