1 #include "game.h" 2 3 namespace client 4 { 5 bool sendplayerinfo = false, sendgameinfo = false, sendcrcinfo = false, loadedmap = false, isready = false, remote = false, demoplayback = false; 6 int needsmap = 0, gettingmap = 0, lastping = 0, sessionid = 0, sessionver = 0, sessionflags = 0, lastplayerinfo = 0, mastermode = 0, needclipboard = -1, demonameid = 0; 7 string connectpass = ""; 8 hashtable<int, const char *>demonames; 9 10 VAR(0, debugmessages, 0, 0, 1); 11 ICOMMAND(0, getready, "", (), intret(isready ? 1 : 0)); 12 ICOMMAND(0, getloadedmap, "", (), intret(maploading || loadedmap ? 1 : 0)); 13 14 SVAR(IDF_PERSIST, demolist, ""); 15 VAR(0, demoendless, 0, 0, 1); 16 VAR(IDF_PERSIST, showpresence, 0, 1, 2); // 0 = never show join/leave, 1 = show only during game, 2 = show when connecting/disconnecting 17 VAR(IDF_PERSIST, showpresencehostinfo, 0, 1, 1); 18 VAR(IDF_PERSIST, showteamchange, 0, 1, 2); // 0 = never show, 1 = show only when switching between, 2 = show when entering match too 19 VAR(IDF_PERSIST, showservervariables, 0, 0, 1); // determines if variables set by the server are printed to the console 20 VAR(IDF_PERSIST, showmapvotes, 0, 1, 3); // shows map votes, 1 = only mid-game (not intermision), 2 = at all times, 3 = verbose 21 22 VAR(IDF_PERSIST, checkpointannounce, 0, 5, 7); // 0 = never, &1 = active players, &2 = all players, &4 = all players in gauntlet 23 VAR(IDF_PERSIST, checkpointannouncefilter, 0, CP_ALL, CP_ALL); // which checkpoint types to announce for 24 VARF(0, checkpointspawn, 0, 1, 1, game::player1->checkpointspawn = checkpointspawn; sendplayerinfo = true); 25 VAR(IDF_PERSIST, demoautoclientsave, 0, 0, 1); 26 ICOMMAND(0, getdemoplayback, "", (), intret(demoplayback ? 1 : 0)); 27 state()28 int state() { return game::player1->state; } 29 ICOMMAND(0, getplayerstate, "", (), intret(state())); 30 maxmsglen()31 int maxmsglen() { return G(messagelength); } 32 33 ICOMMAND(0, numgameplayers, "", (), intret(game::numdynents())); 34 ICOMMAND(0, loopgameplayers, "re", (ident *id, uint *body), 35 { 36 loopstart(id, stack); 37 int numdyns = game::numdynents(); 38 loopi(numdyns) 39 { 40 gameent *d = (gameent *)game::iterdynents(i); 41 if(!d) continue; 42 loopiter(id, stack, d->clientnum); 43 execute(body); 44 } 45 loopend(id, stack); 46 }); 47 otherclients(bool self,bool nospec)48 int otherclients(bool self, bool nospec) 49 { 50 int n = self ? 1 : 0; 51 loopv(game::players) if(game::players[i] && game::players[i]->actortype == A_PLAYER && (!nospec || game::players[i]->state != CS_SPECTATOR)) n++; 52 return n; 53 } 54 ICOMMAND(0, getclientcount, "ii", (int *s, int *n), intret(otherclients(*s!=0, *n!=0))); 55 numplayers()56 int numplayers() 57 { 58 int n = 1; // count ourselves 59 loopv(game::players) if(game::players[i] && game::players[i]->actortype < A_ENEMY) n++; 60 return n; 61 } 62 waiting(bool state)63 int waiting(bool state) 64 { 65 if(!connected(false) || !isready || game::maptime <= 0 || (state && needsmap)) // && otherclients() 66 return state && needsmap ? (gettingmap ? 3 : 2) : 1; 67 return 0; 68 } 69 ICOMMAND(0, waiting, "i", (int *n), intret(waiting(!*n))); 70 71 //*maplist commands: see gamemode.h for related macros 72 73 //getmaplist 74 //arg1: i/gamemode 75 //arg2: i/mutators 76 //returns: list of valid maps including previousmaps validity (cuts previousmaps entries) makemaplist(int g,int m,int c)77 void makemaplist(int g, int m, int c) 78 { 79 char *list = NULL; 80 loopi(2) 81 { 82 maplist(list, g, m, c, mapsfilter, i!=0); 83 if(list) 84 { 85 result(list); 86 DELETEA(list); 87 return; 88 } 89 } 90 } 91 ICOMMAND(0, getmaplist, "iii", (int *g, int *m, int *c), makemaplist(*g, *m, *c)); 92 93 //allowmaplist 94 //arg1: i/gamemode 95 //arg2: i/mutators 96 //returns: list of valid maps regardless of previousmaps presence makeallowmaplist(int g,int m)97 void makeallowmaplist(int g, int m) 98 { 99 char *list = NULL; 100 loopi(2) 101 { 102 allowmaplist(list, g, m); 103 if(list) 104 { 105 result(list); 106 DELETEA(list); 107 return; 108 } 109 } 110 } 111 ICOMMAND(0, allowmaplist, "ii", (int *g, int *m), makeallowmaplist(*g, *m)); 112 113 extern int sortvotes; 114 struct mapvote 115 { 116 vector<gameent *> players; 117 string map; 118 int millis, mode, muts; 119 mapvoteclient::mapvote120 mapvote() {} ~mapvoteclient::mapvote121 ~mapvote() { players.shrink(0); } 122 compareclient::mapvote123 static bool compare(const mapvote &a, const mapvote &b) 124 { 125 if(sortvotes) 126 { 127 if(a.players.length() > b.players.length()) return true; 128 if(a.players.length() < b.players.length()) return false; 129 } 130 return a.millis < b.millis; 131 } 132 }; 133 vector<mapvote> mapvotes; 134 135 VARF(IDF_PERSIST, sortvotes, 0, 0, 1, mapvotes.sort(mapvote::compare)); 136 VARF(IDF_PERSIST, cleanvotes, 0, 0, 1, 137 { 138 if(cleanvotes && !mapvotes.empty()) loopvrev(mapvotes) if(mapvotes[i].players.empty()) mapvotes.remove(i); 139 }); 140 clearvotes(gameent * d,bool msg)141 void clearvotes(gameent *d, bool msg) 142 { 143 int found = 0; 144 loopvrev(mapvotes) if(mapvotes[i].players.find(d) >= 0) 145 { 146 found++; 147 mapvotes[i].players.removeobj(d); 148 if(cleanvotes && mapvotes[i].players.empty()) mapvotes.remove(i); 149 } 150 if(found) 151 { 152 if(!mapvotes.empty()) mapvotes.sort(mapvote::compare); 153 if(msg && showmapvotes >= (d == game::player1 ? 2 : 3)) conoutft(CON_EVENT, "%s cleared their previous vote", game::colourname(d)); 154 } 155 } 156 vote(gameent * d,const char * text,int mode,int muts,bool force=false)157 void vote(gameent *d, const char *text, int mode, int muts, bool force = false) 158 { 159 mapvote *m = NULL; 160 if(!text || !*text) text = "<random>"; 161 if(!mapvotes.empty()) loopvrev(mapvotes) 162 { 163 if(!force && mapvotes[i].players.find(d) >= 0) 164 { 165 if(!strcmp(text, mapvotes[i].map) && mode == mapvotes[i].mode && muts == mapvotes[i].muts) return; 166 mapvotes[i].players.removeobj(d); 167 if(cleanvotes && mapvotes[i].players.empty()) mapvotes.remove(i); 168 } 169 if(!strcmp(text, mapvotes[i].map) && mode == mapvotes[i].mode && muts == mapvotes[i].muts) m = &mapvotes[i]; 170 } 171 if(!m) 172 { 173 m = &mapvotes.add(); 174 copystring(m->map, text); 175 m->mode = mode; 176 m->muts = muts; 177 m->millis = totalmillis ? totalmillis : 1; 178 } 179 m->players.add(d); 180 mapvotes.sort(mapvote::compare); 181 if(showmapvotes >= (!gs_playing(game::gamestate) ? 2 : 1) && !isignored(d->clientnum)) 182 conoutft(CON_EVENT, "%s suggests: \fs\fy%s\fS on \fs\fo%s\fS, press \f{=%s votes} to vote", game::colourname(d), server::gamename(mode, muts), mapctitle(m->map), UI::uiopencmd); 183 } 184 ICOMMAND(0, fakevote, "", (), loopi(20) vote(game::player1, "maps/bloodlust", G_DEATHMATCH, 0, true); loopi(20) vote(game::player1, "maps/dutility", G_CAPTURE, 1<<G_M_GSP1, true)); 185 getvotes(int vote,int prop,int idx)186 void getvotes(int vote, int prop, int idx) 187 { 188 if(vote < 0) intret(mapvotes.length()); 189 else if(mapvotes.inrange(vote)) 190 { 191 mapvote &v = mapvotes[vote]; 192 if(prop < 0) intret(4); 193 else switch(prop) 194 { 195 case 0: 196 if(idx < 0) intret(v.players.length()); 197 else if(v.players.inrange(idx)) intret(v.players[idx]->clientnum); 198 break; 199 case 1: intret(v.mode); break; 200 case 2: intret(v.muts); break; 201 case 3: result(v.map); break; 202 } 203 } 204 } 205 ICOMMAND(0, getvote, "bbb", (int *vote, int *prop, int *idx), getvotes(*vote, *prop, *idx)); 206 207 ICOMMAND(0, loopvotes, "ree", (ident *id, uint *body, uint *none), 208 { 209 loopstart(id, stack); 210 if(mapvotes.empty()) 211 { 212 loopiter(id, stack, -1); 213 execute(none); 214 } 215 else loopv(mapvotes) 216 { 217 loopiter(id, stack, i); 218 execute(body); 219 } 220 loopend(id, stack); 221 }); 222 223 struct demoinfo 224 { 225 demoheader hdr; 226 string file; 227 compareclient::demoinfo228 static int compare(demoinfo &a, demoinfo &b) 229 { 230 return strcmp(a.file, b.file); 231 } 232 }; 233 vector<demoinfo> demoinfos; 234 vector<char *> faildemos; 235 scandemo(const char * name)236 int scandemo(const char *name) 237 { 238 if(!name || !*name) return -1; 239 loopv(demoinfos) if(!strcmp(demoinfos[i].file, name)) return i; 240 loopv(faildemos) if(!strcmp(faildemos[i], name)) return -1; 241 stream *f = opengzfile(name, "rb"); 242 if(!f) 243 { 244 faildemos.add(newstring(name)); 245 return -1; 246 } 247 int num = demoinfos.length(); 248 demoinfo &d = demoinfos.add(); 249 copystring(d.file, name); 250 stringz(msg); 251 if(f->read(&d.hdr, sizeof(demoheader)) != sizeof(demoheader) || memcmp(d.hdr.magic, VERSION_DEMOMAGIC, sizeof(d.hdr.magic))) 252 formatstring(msg, "\frSorry, \fs\fc%s\fS is not a demo file", name); 253 else 254 { 255 lilswap(&d.hdr.gamever, 4); 256 if(d.hdr.gamever != VERSION_GAME) 257 formatstring(msg, "\frDemo \fs\fc%s\fS requires \fs\fc%s\fS version of %s (with protocol version %d)", name, d.hdr.gamever<VERSION_GAME ? "an older" : "a newer", VERSION_NAME, d.hdr.gamever); 258 } 259 delete f; 260 if(msg[0]) 261 { 262 conoutft(CON_DEBUG, "%s", msg); 263 demoinfos.pop(); 264 faildemos.add(newstring(name)); 265 return -1; 266 } 267 return num; 268 } 269 ICOMMAND(0, demoscan, "s", (char *name), intret(scandemo(name))); 270 resetdemos(bool all)271 void resetdemos(bool all) 272 { 273 if(all) loopvrev(demoinfos) demoinfos.remove(i); 274 loopvrev(faildemos) 275 { 276 DELETEA(faildemos[i]); 277 faildemos.remove(i); 278 } 279 } 280 ICOMMAND(0, demoreset, "i", (int *all), resetdemos(*all!=0)); 281 ICOMMAND(0, demosort, "", (), demoinfos.sort(demoinfo::compare)); 282 infodemo(int idx,int prop)283 void infodemo(int idx, int prop) 284 { 285 if(idx < 0) intret(demoinfos.length()); 286 else if(demoinfos.inrange(idx)) 287 { 288 demoinfo &d = demoinfos[idx]; 289 switch(prop) 290 { 291 case 0: intret(d.hdr.gamever); break; 292 case 1: result(d.hdr.mapname); break; 293 case 2: intret(d.hdr.gamemode); break; 294 case 3: intret(d.hdr.mutators); break; 295 case 4: intret(d.hdr.starttime); break; 296 default: break; 297 } 298 } 299 } 300 ICOMMAND(0, demoinfo, "bb", (int *idx, int *prop), infodemo(*idx, *prop)); 301 302 ICOMMAND(0, loopdemos, "rre", (ident *id, ident *id2, uint *body), 303 { 304 loopstart(id, stack); 305 loopstart(id2, stack2); 306 loopv(demoinfos) 307 { 308 loopiter(id, stack, i); 309 loopiter(id2, stack2, demoinfos[i].file); 310 execute(body); 311 } 312 loopend(id, stack); 313 loopend(id2, stack2); 314 }); 315 316 VAR(IDF_PERSIST, authconnect, 0, 1, 1); 317 VAR(IDF_PERSIST, noauthconfig, 0, 0, 1); 318 319 string accountname = "", accountpass = ""; 320 ICOMMAND(0, accountname, "s", (char *s), copystring(accountname, s && *s ? s : "")); 321 ICOMMAND(0, accountpass, "s", (char *s), copystring(accountpass, s && *s ? s : "")); 322 ICOMMAND(0, authkey, "ss", (char *name, char *key), 323 { 324 copystring(accountname, name && *name ? name : ""); 325 copystring(accountpass, key && *key ? key : ""); 326 }); 327 ICOMMAND(0, hasauthkey, "i", (int *n), intret(accountname[0] && accountpass[0] && (!*n || authconnect) ? 1 : 0)); 328 writecfg()329 void writecfg() 330 { 331 if(noauthconfig || !*accountname || !*accountpass) return; 332 stream *f = openutf8file("auth.cfg", "w"); 333 if(!f) return; 334 f->printf("authkey %s %s\n", accountname, accountpass); 335 delete f; 336 } 337 writegamevars(const char * name,bool all=false,bool server=false)338 void writegamevars(const char *name, bool all = false, bool server = false) 339 { 340 if(!name || !*name) name = "vars.cfg"; 341 stream *f = openfile(name, "w"); 342 if(!f) return; 343 vector<ident *> ids; 344 enumerate(idents, ident, id, ids.add(&id)); 345 ids.sortname(); 346 loopv(ids) 347 { 348 ident &id = *ids[i]; 349 if(id.flags&IDF_CLIENT && !(id.flags&IDF_READONLY) && !(id.flags&IDF_WORLD)) switch(id.type) 350 { 351 case ID_VAR: 352 if(*id.storage.i == id.def.i) 353 { 354 if(all) f->printf("// "); 355 else break; 356 } 357 if(server) f->printf("sv_"); 358 f->printf("%s %s\n", escapeid(id), intstr(&id)); 359 break; 360 case ID_FVAR: 361 if(*id.storage.f == id.def.f) 362 { 363 if(all) f->printf("// "); 364 else break; 365 } 366 if(server) f->printf("sv_"); 367 f->printf("%s %s\n", escapeid(id), floatstr(*id.storage.f)); 368 break; 369 case ID_SVAR: 370 if(!strcmp(*id.storage.s, id.def.s)) 371 { 372 if(all) f->printf("// "); 373 else break; 374 } 375 if(server) f->printf("sv_"); 376 f->printf("%s %s\n", escapeid(id), escapestring(*id.storage.s)); 377 break; 378 } 379 } 380 delete f; 381 } 382 ICOMMAND(0, writevars, "sii", (char *name, int *all, int *sv), if(!(identflags&IDF_WORLD)) writegamevars(name, *all!=0, *sv!=0)); 383 writegamevarsinfo(const char * name)384 void writegamevarsinfo(const char *name) 385 { 386 if(!name || !*name) name = "varsinfo.txt"; 387 stream *f = openfile(name, "w"); 388 if(!f) return; 389 f->printf("// List of vars properties, fields are separated by tabs; empty if nonexistent\n"); 390 f->printf("// Fields: NAME TYPE FLAGS ARGS VALTYPE VALUE MIN MAX DESC USAGE\n"); 391 vector<ident *> ids; 392 enumerate(idents, ident, id, ids.add(&id)); 393 ids.sortname(); 394 loopv(ids) 395 { 396 ident &id = *ids[i]; 397 if(!(id.flags&IDF_SERVER)) // Exclude sv_* duplicates 398 { 399 f->printf("%s\t%d\t%d", id.name, id.type, id.flags); 400 switch(id.type) 401 { 402 case ID_VAR: 403 f->printf("\t\t"); // empty ARGS VALTYPE 404 if(!(id.flags&IDF_HEX)) 405 f->printf("\t%d\t%d\t%d", id.def.i, id.minval, id.maxval); 406 else 407 { 408 if(id.maxval == 0xFFFFFF) 409 f->printf("\t0x%.6X\t0x%.6X\t0x%.6X", id.def.i, id.minval, id.maxval); 410 else if(uint(id.maxval) == 0xFFFFFFFFU) 411 f->printf("\t0x%.8X\t0x%.8X\t0x%.8X", uint(id.def.i), uint(id.minval), uint(id.maxval)); 412 else 413 f->printf("\t0x%X\t0x%X\t0x%X", id.def.i, id.minval, id.maxval); 414 } 415 break; 416 case ID_FVAR: 417 f->printf("\t\t\t%s\t%s\t%s", floatstr(id.def.f), floatstr(id.minvalf), floatstr(id.maxvalf)); // empty ARGS VALTYPE 418 break; 419 case ID_SVAR: 420 f->printf("\t\t\t%s\t\t", escapestring(id.def.s)); // empty ARGS VALTYPE MIN MAX 421 break; 422 case ID_ALIAS: 423 f->printf("\t\t%d", id.valtype); // empty ARGS 424 switch(id.valtype) 425 { 426 case VAL_NULL: 427 f->printf("\tNULL"); 428 break; 429 case VAL_INT: 430 f->printf("\t%d", id.val.i); 431 break; 432 case VAL_FLOAT: 433 f->printf("\t%s", floatstr(id.val.f)); 434 break; 435 case VAL_STR: 436 f->printf("\t%s", escapestring(id.val.s)); 437 break; 438 case VAL_CSTR: 439 f->printf("\t%s", escapestring(id.val.cstr)); 440 break; 441 } 442 f->printf("\t\t"); // empty MIN MAX 443 break; 444 case ID_COMMAND: 445 f->printf("\t%s\t\t\t\t", escapestring(id.args)); // empty VALTYPE VALUE MIN MAX 446 break; 447 } 448 // empty if nonexistent 449 f->printf("\t%s", escapestring(id.desc ? id.desc : "")); 450 stringz(fields); 451 loopvj(id.fields) concformatstring(fields, "%s%s", j ? " " : "", id.fields[j]); 452 f->printf("\t%s", escapestring(*fields ? fields : "")); 453 f->printf("\n"); 454 } 455 } 456 delete f; 457 } 458 ICOMMAND(0, writevarsinfo, "s", (char *name), if(!(identflags&IDF_WORLD)) writegamevarsinfo(name)); 459 460 // collect c2s messages conveniently 461 vector<uchar> messages; 462 bool messagereliable = false; 463 464 VAR(IDF_PERSIST, colourchat, 0, 1, 1); 465 SVAR(IDF_PERSIST, filterwords, ""); 466 467 VAR(IDF_PERSIST, showlaptimes, 0, 2, 3); // 0 = off, 1 = only player, 2 = +humans, 3 = +bots 468 defaultserversort()469 const char *defaultserversort() 470 { 471 static string vals; 472 formatstring(vals, "%d %d %d", SINFO_NUMPLRS, SINFO_PRIO, SINFO_PING); 473 return vals; 474 } 475 resetserversort()476 void resetserversort() 477 { 478 setsvarchecked(getident("serversort"), defaultserversort()); 479 } 480 ICOMMAND(0, serversortreset, "", (), resetserversort()); 481 482 vector<int> serversortstyles; 483 void updateserversort(); 484 SVARF(IDF_PERSIST, serversort, defaultserversort(), 485 { 486 if(!serversort[0] || serversort[0] == '[') 487 { 488 delete[] serversort; 489 serversort = newstring(defaultserversort()); 490 } 491 updateserversort(); 492 }); 493 updateserversort()494 void updateserversort() 495 { 496 vector<char *> styles; 497 explodelist(serversort, styles, SINFO_MAX); 498 serversortstyles.setsize(0); 499 loopv(styles) serversortstyles.add(parseint(styles[i])); 500 styles.deletearrays(); 501 } 502 getvitem(gameent * d,int n,int v)503 void getvitem(gameent *d, int n, int v) 504 { 505 if(n < 0) intret(d->vitems.length()); 506 else if(v < 0) intret(2); 507 else if(d->vitems.inrange(n)) switch(v) 508 { 509 case 0: intret(d->vitems[n]); break; 510 case 1: if(vanities.inrange(d->vitems[n])) result(vanities[d->vitems[n]].ref); break; 511 default: break; 512 } 513 } 514 ICOMMAND(0, getplayervanity, "", (), result(game::player1->vanity)); 515 ICOMMAND(0, getplayervitem, "bi", (int *n, int *v), getvitem(game::player1, *n, *v)); 516 517 ICOMMAND(0, mastermode, "i", (int *val), addmsg(N_MASTERMODE, "ri", *val)); 518 ICOMMAND(0, getplayername, "", (), result(game::player1->name)); 519 ICOMMAND(0, getplayercolour, "bg", (int *m, int *f), intret(game::getcolour(game::player1, *m, *f >= 0 && *f <= 10 ? *f : 1.f))); 520 ICOMMAND(0, getplayermodel, "", (), intret(game::player1->model)); 521 ICOMMAND(0, getplayerpattern, "", (), intret(game::player1->pattern)); 522 ICOMMAND(0, getplayerteam, "i", (int *p), *p ? intret(game::player1->team) : result(TEAM(game::player1->team, name))); 523 ICOMMAND(0, getplayerteamicon, "", (), result(hud::teamtexname(game::player1->team))); 524 ICOMMAND(0, getplayerteamcolour, "", (), intret(TEAM(game::player1->team, colour))); 525 ICOMMAND(0, getplayercn, "", (), intret(game::player1->clientnum)); 526 getname()527 const char *getname() { return game::player1->name; } 528 setplayername(const char * name)529 void setplayername(const char *name) 530 { 531 if(name && *name) 532 { 533 string namestr; 534 filterstring(namestr, name, true, true, true, true, MAXNAMELEN); 535 if(*namestr && strcmp(game::player1->name, namestr)) 536 { 537 game::player1->setname(namestr); 538 if(initing == NOT_INITING) conoutft(CON_EVENT, "\fm* you are now known as %s", game::player1->name); 539 sendplayerinfo = true; 540 } 541 } 542 } 543 SVARF(IDF_PERSIST, playername, "", setplayername(playername)); 544 setplayercolour(int colour)545 void setplayercolour(int colour) 546 { 547 if(colour >= 0 && colour <= 0xFFFFFF && game::player1->colour != colour) 548 { 549 game::player1->colour = colour; 550 sendplayerinfo = true; 551 } 552 } 553 VARF(IDF_PERSIST|IDF_HEX, playercolour, -1, -1, 0xFFFFFF, setplayercolour(playercolour)); 554 setplayermodel(int model)555 void setplayermodel(int model) 556 { 557 if(model >= 0 && game::player1->model != model) 558 { 559 game::player1->model = model; 560 sendplayerinfo = true; 561 } 562 } 563 VARF(IDF_PERSIST, playermodel, 0, 0, PLAYERTYPES-1, setplayermodel(playermodel)); 564 setplayerpattern(int pattern)565 void setplayerpattern(int pattern) 566 { 567 if(pattern >= 0 && game::player1->pattern != pattern) 568 { 569 game::player1->pattern = pattern; 570 sendplayerinfo = true; 571 } 572 } 573 VARF(IDF_PERSIST, playerpattern, 0, 0, PLAYERPATTERNS-1, setplayerpattern(playerpattern)); 574 575 SVARF(IDF_PERSIST, playervanity, "", if(game::player1->setvanity(playervanity)) sendplayerinfo = true;); 576 setloadweap(const char * list)577 void setloadweap(const char *list) 578 { 579 vector<int> items; 580 if(list && *list) 581 { 582 vector<char *> chunk; 583 explodelist(list, chunk); 584 loopv(chunk) 585 { 586 if(!chunk[i] || !*chunk[i] || !isnumeric(*chunk[i])) continue; 587 int v = parseint(chunk[i]); 588 items.add(v >= W_OFFSET && v < W_ITEM ? v : 0); 589 } 590 chunk.deletearrays(); 591 } 592 game::player1->loadweap.shrink(0); 593 loopv(items) if(game::player1->loadweap.find(items[i]) < 0) 594 { 595 game::player1->loadweap.add(items[i]); 596 if(game::player1->loadweap.length() >= W_LOADOUT) break; 597 } 598 sendplayerinfo = true; 599 } 600 SVARF(IDF_PERSIST, playerloadweap, "", setloadweap(playerloadweap)); 601 setrandweap(const char * list)602 void setrandweap(const char *list) 603 { 604 vector<int> items; 605 if(list && *list) 606 { 607 vector<char *> chunk; 608 explodelist(list, chunk); 609 loopv(chunk) 610 { 611 if(!chunk[i] || !*chunk[i] || !isnumeric(*chunk[i])) continue; 612 int v = parseint(chunk[i]); 613 items.add(v ? 1 : 0); 614 } 615 chunk.deletearrays(); 616 } 617 game::player1->randweap.shrink(0); 618 loopv(items) 619 { 620 game::player1->randweap.add(items[i]); 621 if(game::player1->randweap.length() >= W_LOADOUT) break; 622 } 623 sendplayerinfo = true; 624 } 625 SVARF(IDF_PERSIST, playerrandweap, "", setrandweap(playerrandweap)); 626 627 ICOMMAND(0, getrandweap, "i", (int *n), intret(game::player1->randweap.inrange(*n) ? game::player1->randweap[*n] : 1)); 628 ICOMMAND(0, getloadweap, "i", (int *n), intret(game::player1->loadweap.inrange(*n) ? game::player1->loadweap[*n] : -1)); 629 ICOMMAND(0, allowedweap, "i", (int *n), intret(isweap(*n) && m_check(W(*n, modes), W(*n, muts), game::gamemode, game::mutators) && !W(*n, disabled) ? 1 : 0)); 630 ICOMMAND(0, hasloadweap, "bb", (int *g, int *m), intret(m_loadout(m_game(*g) ? *g : game::gamemode, *m >= 0 ? *m : game::mutators) ? 1 : 0)); 631 teamfromname(const char * team)632 int teamfromname(const char *team) 633 { 634 if(m_team(game::gamemode, game::mutators)) 635 { 636 if(team[0]) 637 { 638 int t = atoi(team); 639 loopi(numteams(game::gamemode, game::mutators)) 640 { 641 if((t && t == i+T_FIRST) || !strcasecmp(TEAM(i+T_FIRST, name), team)) 642 { 643 return i+T_FIRST; 644 } 645 } 646 } 647 return T_FIRST; 648 } 649 return T_NEUTRAL; 650 } 651 switchteam(const char * team)652 void switchteam(const char *team) 653 { 654 if(team[0]) 655 { 656 if(m_team(game::gamemode, game::mutators)) 657 { 658 int t = teamfromname(team); 659 if(isteam(game::gamemode, game::mutators, t, T_FIRST)) addmsg(N_SWITCHTEAM, "ri", t); 660 } 661 else conoutft(CON_DEBUG, "\frCan only change teams when actually playing in team games"); 662 } 663 else conoutft(CON_DEBUG, "\fgYour team is: %s", game::colourteam(game::player1->team)); 664 } 665 ICOMMAND(0, team, "s", (char *s), switchteam(s)); 666 allowedittoggle(bool edit)667 bool allowedittoggle(bool edit) 668 { 669 bool allow = edit || m_edit(game::gamemode); // && game::player1->state == CS_ALIVE); 670 if(!allow) conoutft(CON_DEBUG, "\frYou must start an editing game to edit the map"); 671 return allow; 672 } 673 edittoggled(bool edit)674 void edittoggled(bool edit) 675 { 676 if(!edit && (game::maptime <= 0 || game::player1->state != CS_EDITING)) return; 677 game::player1->editspawn(game::gamemode, game::mutators); 678 game::player1->state = edit ? CS_EDITING : (m_edit(game::gamemode) ? CS_ALIVE : CS_DEAD); 679 game::player1->o = camera1->o; 680 game::player1->yaw = camera1->yaw; 681 game::player1->pitch = camera1->pitch; 682 game::player1->resetinterp(); 683 game::resetstate(); 684 game::specreset(); 685 physics::entinmap(game::player1, true); // find spawn closest to current floating pos 686 projs::removeplayer(game::player1); 687 if(m_edit(game::gamemode)) addmsg(N_EDITMODE, "ri", edit ? 1 : 0); 688 } 689 690 ICOMMAND(0, getclientfocused, "", (), intret(game::focus ? game::focus->clientnum : game::player1->clientnum)); 691 getcn(physent * d)692 int getcn(physent *d) 693 { 694 if(!d || !gameent::is(d)) return -1; 695 return ((gameent *)d)->clientnum; 696 } 697 parseplayer(const char * arg)698 int parseplayer(const char *arg) 699 { 700 if(!arg || !*arg) return game::player1->clientnum; 701 char *end; 702 int n = strtol(arg, &end, 10); 703 if(*arg && !*end) 704 { 705 if(n != game::player1->clientnum && !game::players.inrange(n)) return -1; 706 return n; 707 } 708 #define PARSEPLAYER(op,val) \ 709 { \ 710 gameent *o = game::player1; \ 711 if(!op(arg, val)) return o->clientnum; \ 712 loopv(game::players) if(game::players[i]) \ 713 { \ 714 o = game::players[i]; \ 715 if(!op(arg, val)) return o->clientnum; \ 716 } \ 717 } 718 PARSEPLAYER(strcmp, game::colourname(o, NULL, false, true, 0)); 719 PARSEPLAYER(strcasecmp, game::colourname(o, NULL, false, true, 0)); 720 PARSEPLAYER(strcmp, o->name); 721 PARSEPLAYER(strcasecmp, o->name); 722 #define PARSEPLAYERN(op,val) \ 723 { \ 724 gameent *o = game::player1; \ 725 if(!op(arg, val, len)) return o->clientnum; \ 726 loopv(game::players) if(game::players[i]) \ 727 { \ 728 o = game::players[i]; \ 729 if(!op(arg, val, len)) return o->clientnum; \ 730 } \ 731 } 732 size_t len = strlen(arg); 733 PARSEPLAYERN(strncmp, game::colourname(o, NULL, false, true, 0)); 734 PARSEPLAYERN(strncasecmp, game::colourname(o, NULL, false, true, 0)); 735 PARSEPLAYERN(strncmp, o->name); 736 PARSEPLAYERN(strncasecmp, o->name); 737 return -1; 738 } 739 ICOMMAND(IDF_NAMECOMPLETE, getclientnum, "s", (char *who), intret(parseplayer(who))); 740 listclients(bool local,int noai)741 void listclients(bool local, int noai) 742 { 743 vector<char> buf; 744 string cn; 745 int numclients = 0; 746 if(local) 747 { 748 formatstring(cn, "%d", game::player1->clientnum); 749 buf.put(cn, strlen(cn)); 750 numclients++; 751 } 752 loopv(game::players) if(game::players[i] && (!noai || game::players[i]->actortype > (noai >= 2 ? A_PLAYER : A_BOT))) 753 { 754 formatstring(cn, "%d", game::players[i]->clientnum); 755 if(numclients++) buf.add(' '); 756 buf.put(cn, strlen(cn)); 757 } 758 buf.add('\0'); 759 result(buf.getbuf()); 760 } 761 ICOMMAND(0, listclients, "ii", (int *local, int *noai), listclients(*local!=0, *noai)); 762 getlastclientnum()763 void getlastclientnum() 764 { 765 int cn = game::player1->clientnum; 766 loopv(game::players) if(game::players[i] && game::players[i]->clientnum > cn) cn = game::players[i]->clientnum; 767 intret(cn); 768 } 769 ICOMMAND(0, getlastclientnum, "", (), getlastclientnum()); 770 771 #define LOOPCLIENTS(name,op,lp,nop) \ 772 ICOMMAND(0, loopclients##name, "iire", (int *count, int *skip, ident *id, uint *body), \ 773 { \ 774 loopstart(id, stack); \ 775 int amt = 1; \ 776 loopv(game::players) if(game::players[i]) amt++; \ 777 op(amt, *count, *skip) \ 778 { \ 779 int r = -1; \ 780 int n = nop ? amt-1 : 0; \ 781 if(!i) \ 782 { \ 783 if(nop ? n <= i : n >= i) r = game::player1->clientnum; \ 784 if(nop) n--; \ 785 else n++; \ 786 } \ 787 else \ 788 { \ 789 lp(game::players) if(game::players[k]) \ 790 { \ 791 if(nop ? n <= i : n >= i) \ 792 { \ 793 r = game::players[k]->clientnum; \ 794 break; \ 795 } \ 796 if(nop) n--; \ 797 else n++; \ 798 } \ 799 } \ 800 if(r >= 0) \ 801 { \ 802 loopiter(id, stack, r); \ 803 execute(body); \ 804 } \ 805 } \ 806 loopend(id, stack); \ 807 }); 808 LOOPCLIENTS(,loopcsi,loopvk,false); 809 LOOPCLIENTS(rev,loopcsirev,loopvkrev,true); 810 811 #define LOOPCLIENTSIF(name,op,lp,nop) \ 812 ICOMMAND(0, loopclients##name##if, "iiree", (int *count, int *skip, ident *id, uint *cond, uint *body), \ 813 { \ 814 loopstart(id, stack); \ 815 int amt = 1; \ 816 loopv(game::players) if(game::players[i]) amt++; \ 817 op(amt, *count, *skip) \ 818 { \ 819 int r = -1; \ 820 int n = nop ? amt-1 : 0; \ 821 if(!i) \ 822 { \ 823 if(nop ? n <= i : n >= i) r = game::player1->clientnum; \ 824 if(nop) n--; \ 825 else n++; \ 826 } \ 827 else \ 828 { \ 829 lp(game::players) if(game::players[k]) \ 830 { \ 831 if(nop ? n <= i : n >= i) \ 832 { \ 833 r = game::players[k]->clientnum; \ 834 break; \ 835 } \ 836 if(nop) n--; \ 837 else n++; \ 838 } \ 839 } \ 840 if(r >= 0) \ 841 { \ 842 loopiter(id, stack, r); \ 843 if(executebool(cond)) execute(body); \ 844 } \ 845 } \ 846 loopend(id, stack); \ 847 }); 848 LOOPCLIENTSIF(,loopcsi,loopvk,false); 849 LOOPCLIENTSIF(rev,loopcsirev,loopvkrev,true); 850 851 #define LOOPINVENTORY(name,op,lp,nop) \ 852 ICOMMAND(IDF_NAMECOMPLETE, loopinventory##name, "siire", (char *who, int *count, int *skip, ident *id, uint *body), \ 853 { \ 854 gameent *d = game::getclient(parseplayer(who)); \ 855 if(!d) return; \ 856 loopstart(id, stack); \ 857 int amt = d->holdweapcount(m_weapon(d->actortype, game::gamemode, game::mutators), lastmillis); \ 858 op(amt, *count, *skip) \ 859 { \ 860 int r = -1; \ 861 int n = nop ? amt-1 : 0; \ 862 lp(W_ALL) if(d->holdweap(k, m_weapon(d->actortype, game::gamemode, game::mutators), lastmillis)) \ 863 { \ 864 if(nop ? n <= i : n >= i) \ 865 { \ 866 r = k; \ 867 break; \ 868 } \ 869 if(nop) n--; \ 870 else n++; \ 871 } \ 872 if(r >= 0) \ 873 { \ 874 loopiter(id, stack, r); \ 875 execute(body); \ 876 } \ 877 } \ 878 loopend(id, stack); \ 879 }); 880 LOOPINVENTORY(,loopcsi,loopk,false); 881 LOOPINVENTORY(rev,loopcsirev,loopkrev,true); 882 883 VAR(0, numplayertypes, 1, PLAYERTYPES, -1); 884 ICOMMAND(0, getmodelname, "ib", (int *mdl, int *idx), result(*mdl >= 0 ? playertypes[*mdl%PLAYERTYPES][*idx >= 0 ? clamp(*idx, 0, 6) : 6] : "")); 885 VAR(0, numpatterns, 1, PLAYERPATTERNS, -1); 886 ICOMMAND(0, getpattern, "ib", (int *pattern, int *idx), 887 if(*pattern >= 0) 888 { 889 const ::playerpattern &p = playerpatterns[*pattern%PLAYERPATTERNS]; 890 switch(*idx) 891 { 892 case 0: result(p.filename); break; 893 case 1: result(p.id); break; 894 case 2: result(p.name); break; 895 case 3: intret(p.clamp); break; 896 case 4: intret(p.scale); break; 897 default: break; 898 } 899 } 900 ); 901 902 ICOMMAND(0, getcamerayaw, "", (), floatret(camera1->yaw)); 903 ICOMMAND(0, getcamerapitch, "", (), floatret(camera1->pitch)); 904 ICOMMAND(0, getcameraroll, "", (), floatret(camera1->roll)); 905 ICOMMAND(0, getcameraoffyaw, "f", (float *yaw), floatret(*yaw-camera1->yaw)); 906 907 CLCOMMANDK(presence, intret(1), intret(0)); 908 CLCOMMAND(yaw, floatret(d->yaw)); 909 CLCOMMAND(pitch, floatret(d->pitch)); 910 CLCOMMAND(roll, floatret(d->roll)); 911 radarallow(gameent * d,vec & dir,float & dist)912 bool radarallow(gameent *d, vec &dir, float &dist) 913 { 914 if(m_hard(game::gamemode, game::mutators) || d == game::focus || d->actortype >= A_ENEMY) return false; 915 if(d->state != CS_ALIVE && d->state != CS_EDITING && d->state != CS_DEAD && (!d->lastdeath || d->state != CS_WAITING)) return false; 916 if(m_duke(game::gamemode, game::mutators) && (!d->lastdeath || lastmillis-d->lastdeath >= 1000)) return false; 917 bool dominated = game::focus->dominated.find(d) >= 0; 918 if(!dominated && vec(d->vel).add(d->falling).magnitude() <= 0) return false; 919 dir = vec(d->center()).sub(camera1->o); 920 dist = dir.magnitude(); 921 if(!dominated && hud::radarlimited(dist)) return false; 922 return true; 923 } 924 CLCOMMAND(radarallow, 925 { 926 vec dir(0, 0, 0); 927 float dist = -1; 928 intret(radarallow(d, dir, dist) ? 1 : 0); 929 }); 930 CLCOMMAND(radardist, 931 { 932 vec dir(0, 0, 0); 933 float dist = -1; 934 if(!radarallow(d, dir, dist)) return; 935 floatret(dist); 936 }); 937 CLCOMMAND(radardir, 938 { 939 vec dir(0, 0, 0); 940 float dist = -1; 941 if(!radarallow(d, dir, dist)) return; 942 dir.rotate_around_z(-camera1->yaw*RAD).normalize(); 943 floatret(-atan2(dir.x, dir.y)/RAD); 944 }); 945 CLCOMMAND(radaryaw, 946 { 947 vec dir(0, 0, 0); 948 float dist = -1; 949 if(!radarallow(d, dir, dist)) return; 950 floatret(d->yaw-camera1->yaw); 951 }); 952 953 CLCOMMANDM(name, "sbbb", (char *who, int *colour, int *icon, int *dupname), result(game::colourname(d, NULL, *icon!=0, *dupname!=0, *colour >= 0 ? *colour : 3))); 954 CLCOMMANDM(colour, "sbg", (char *who, int *m, float *f), intret(game::getcolour(d, *m, *f >= 0 && *f <= 10 ? *f : 1.f))); 955 CLCOMMANDM(vitem, "sbi", (char *who, int *n, int *v), getvitem(d, *n, *v)); 956 957 CLCOMMAND(weapselect, intret(d->weapselect)); 958 CLCOMMANDM(loadweap, "si", (char *who, int *n), intret(d->loadweap.inrange(*n) ? d->loadweap[*n] : -1)); 959 CLCOMMANDM(weapget, "siii", (char *who, int *n, int *a, int *b), intret(isweap(*n) ? d->getammo(*n, *a!=0 ? lastmillis : 0, *b!=0) : -1)); 960 CLCOMMANDM(weapammo, "sii", (char *who, int *n, int *m), intret(isweap(*n) ? d->weapammo[*n][clamp(*m, 0, W_A_MAX-1)] : -1)); 961 CLCOMMANDM(weapclip, "si", (char *who, int *n), intret(isweap(*n) ? d->weapammo[*n][W_A_CLIP] : -1)); 962 CLCOMMANDM(weapstore, "si", (char *who, int *n), intret(isweap(*n) ? d->weapammo[*n][W_A_STORE] : -1)); 963 CLCOMMANDM(weapstate, "si", (char *who, int *n), intret(isweap(*n) ? d->weapstate[*n] : W_S_IDLE)); 964 CLCOMMANDM(weaptime, "si", (char *who, int *n), intret(isweap(*n) ? d->weaptime[*n] : 0)); 965 CLCOMMANDM(weapwait, "si", (char *who, int *n), intret(isweap(*n) ? d->weapwait[*n] : 0)); 966 CLCOMMANDM(weapload, "sii", (char *who, int *n, int *m), intret(isweap(*n) ? d->weapload[*n][clamp(*m, 0, W_A_MAX-1)] : 0)); 967 CLCOMMANDM(weaphold, "si", (char *who, int *n), intret(isweap(*n) && d->holdweap(*n, m_weapon(d->actortype, game::gamemode, game::mutators), lastmillis) ? 1 : 0)); 968 CLCOMMAND(weapholdnum, intret(d->holdweapcount(m_weapon(d->actortype, game::gamemode, game::mutators), lastmillis))); 969 CLCOMMANDM(action, "sb", (char *who, int *n), intret(d->action[clamp(*n, 0, int(AC_MAX-1))] ? 1 : 0)); 970 CLCOMMANDM(actiontime, "sb", (char *who, int *n), intret(d->actiontime[clamp(*n, 0, int(AC_MAX-1))])); 971 972 CLCOMMAND(move, intret(d->move)); 973 CLCOMMAND(strafe, intret(d->strafe)); 974 CLCOMMAND(turnside, intret(d->turnside)); 975 CLCOMMAND(physstate, intret(d->physstate)); 976 CLCOMMAND(lastdeath, intret(d->lastdeath)); 977 CLCOMMAND(lastspawn, intret(d->lastspawn)); 978 CLCOMMAND(lastbuff, intret(d->lastbuff)); 979 CLCOMMAND(lastshoot, intret(d->lastshoot)); 980 CLCOMMAND(airmillis, intret(d->airmillis)); 981 CLCOMMAND(floormillis, intret(d->floormillis)); 982 CLCOMMAND(inliquid, intret(d->inliquid ? 1 : 0)); 983 CLCOMMAND(onladder, intret(d->onladder ? 1 : 0)); 984 CLCOMMAND(headless, intret(d->headless ? 1 : 0)); 985 CLCOMMAND(obliterated, intret(d->obliterated ? 1 : 0)); 986 CLCOMMAND(actortype, intret(d->actortype)); 987 CLCOMMAND(pcolour, intret(d->colour)); 988 CLCOMMAND(model, intret(d->model%PLAYERTYPES)); 989 CLCOMMAND(pattern, intret(d->pattern%PLAYERPATTERNS)); 990 CLCOMMAND(vanity, result(d->vanity)); 991 CLCOMMAND(handle, result(d->handle)); 992 CLCOMMAND(steamid, result(d->steamid)); 993 CLCOMMAND(host, result(d->hostip)); 994 CLCOMMAND(ip, result(d->hostip)); 995 CLCOMMAND(ping, intret(d->ping)); 996 CLCOMMAND(pj, intret(d->plag)); 997 CLCOMMAND(team, intret(d->team)); 998 CLCOMMAND(state, intret(d->state)); 999 CLCOMMAND(health, intret(d->health)); 1000 CLCOMMAND(points, intret(d->points)); 1001 CLCOMMAND(cptime, intret(d->cptime)); 1002 CLCOMMAND(cplast, intret(d->cplast)); 1003 CLCOMMAND(cpmillis, intret(d->cpmillis ? lastmillis-d->cpmillis : 0)); 1004 CLCOMMAND(frags, intret(d->frags)); 1005 CLCOMMAND(deaths, intret(d->deaths)); 1006 CLCOMMAND(totalpoints, intret(d->totalpoints)); 1007 CLCOMMAND(totalfrags, intret(d->totalfrags)); 1008 CLCOMMAND(totaldeaths, intret(d->totaldeaths)); 1009 CLCOMMAND(totalavgpos, floatret(d->totalavgpos)); 1010 CLCOMMAND(balancescore, floatret(d->balancescore())); 1011 CLCOMMAND(timeplayed, intret(d->updatetimeplayed())); 1012 1013 CLCOMMAND(speed, floatret(d->speed)); 1014 CLCOMMAND(jumpspeed, floatret(d->jumpspeed)); 1015 CLCOMMAND(impulsespeed, floatret(d->impulsespeed)); 1016 CLCOMMAND(weight, floatret(d->weight)); 1017 1018 CLCOMMAND(scoretime, floatret(d->scoretime())); 1019 CLCOMMANDM(kdratio, "si", (char *who, int *n), intret(d->kdratio(*n!=0))); 1020 1021 CLCOMMAND(allowimpulse, intret(physics::allowimpulse(d) ? 1 : 0)); 1022 CLCOMMAND(impulsemeter, intret(d->impulse[IM_METER])); // IM_METER = 0, IM_TYPE, IM_TIME, IM_REGEN, IM_COUNT, IM_COLLECT, IM_SLIP, IM_SLIDE, IM_JUMP, IM_MAX 1023 CLCOMMAND(impulsetype, intret(d->impulse[IM_TYPE])); 1024 CLCOMMANDM(impulsetimer, "b", (char *who, int *n), intret(d->impulsetime[*n >= 0 && *n < IM_T_MAX ? *n : d->impulse[IM_TYPE]])); 1025 CLCOMMAND(impulseregen, intret(d->impulse[IM_REGEN])); 1026 CLCOMMAND(impulsecount, intret(d->impulse[IM_COUNT])); 1027 CLCOMMAND(impulsecollect, intret(d->impulse[IM_COLLECT])); 1028 CLCOMMAND(impulseslip, intret(d->impulse[IM_SLIP])); 1029 CLCOMMAND(impulsejump, intret(d->impulsetime[IM_T_JUMP])); 1030 CLCOMMAND(impulsewait, intret(d->impulsetime[IM_T_PUSHER])); 1031 CLCOMMANDM(impulse, "si", (char *who, int *n), intret(*n >= 0 && *n < IM_MAX ? d->impulse[*n] : 0)); 1032 1033 CLCOMMAND(buffing, intret(d->lastbuff)); 1034 CLCOMMAND(burning, intret(d->burntime ? d->burning(lastmillis, d->burntime) : 0)); 1035 CLCOMMAND(bleeding, intret(d->bleedtime ? d->bleeding(lastmillis, d->bleedtime) : 0)); 1036 CLCOMMAND(shocking, intret(d->shocktime ? d->shocking(lastmillis, d->shocktime) : 0)); 1037 CLCOMMAND(regen, intret(regentime ? d->lastregen : 0)); 1038 CLCOMMAND(impulselast, intret(game::canregenimpulse(d) && d->impulse[IM_METER] > 0 && d->lastimpulsecollect ? (lastmillis-d->lastimpulsecollect)%1000 : 0)); 1039 1040 CLCOMMAND(spawnweap, intret(m_weapon(d->actortype, game::gamemode, game::mutators))); 1041 CLCOMMAND(spawndelay, intret(m_delay(d->actortype, game::gamemode, game::mutators, d->team))); 1042 CLCOMMAND(spawnprotect, intret(m_protect(game::gamemode, game::mutators))); 1043 CLCOMMAND(spawnhealth, intret(d->gethealth(game::gamemode, game::mutators))); 1044 CLCOMMAND(maxhealth, intret(d->gethealth(game::gamemode, game::mutators, true))); 1045 1046 CLCOMMANDM(rescolour, "sib", (char *who, int *n, int *c), intret(game::pulsehexcol(d, *n, *c > 0 ? *c : 50))); 1047 CLCOMMANDM(velocity, "si", (char *who, int *n), floatret(vec(d->vel).add(d->falling).magnitude()*(*n!=0 ? (*n > 0 ? 3.6f/8.f : 0.125f) : 1.f))); 1048 1049 #define CLDOMCMD(dtype) \ 1050 CLCOMMANDM(dtype, "sb", (char *who, int *n), \ 1051 { \ 1052 if(*n < 0) intret(d->dtype.length()); \ 1053 else if(d->dtype.inrange(*n)) intret(d->dtype[*n]->clientnum); \ 1054 }); 1055 CLDOMCMD(dominating); 1056 CLDOMCMD(dominated); 1057 1058 #define CLISDOMCMD(dtype) \ 1059 CLCOMMANDMK(is##dtype, "ss", (char *who, char *n), \ 1060 { \ 1061 gameent *e = game::getclient(client::parseplayer(n)); \ 1062 if(!e) \ 1063 { \ 1064 intret(0); \ 1065 return; \ 1066 } \ 1067 loopv(d->dtype) if(d->dtype[i]->clientnum == e->clientnum) \ 1068 { \ 1069 intret(1); \ 1070 return; \ 1071 } \ 1072 intret(0); \ 1073 return; \ 1074 }, intret(0); return); 1075 CLISDOMCMD(dominating); 1076 CLISDOMCMD(dominated); 1077 1078 CLCOMMAND(privilege, intret(d->privilege&PRIV_TYPE)); 1079 CLCOMMAND(privlocal, intret(d->privilege&PRIV_LOCAL ? 1 : 0)); 1080 CLCOMMAND(privtex, result(hud::privtex(d->privilege, d->actortype))); haspriv(gameent * d,int priv)1081 bool haspriv(gameent *d, int priv) 1082 { 1083 if(!d) return false; 1084 if(!priv || (d == game::player1 && !remote)) return true; 1085 return (d->privilege&PRIV_TYPE) >= priv; 1086 } 1087 #define CLPRIVCMD(pname,pval) CLCOMMAND(priv##pname, intret(haspriv(d, pval) ? 1 : 0)); 1088 CLPRIVCMD(none, PRIV_NONE); 1089 CLPRIVCMD(player, PRIV_PLAYER); 1090 CLPRIVCMD(supporter, PRIV_SUPPORTER); 1091 CLPRIVCMD(moderator, PRIV_MODERATOR); 1092 CLPRIVCMD(administrator, PRIV_ADMINISTRATOR); 1093 CLPRIVCMD(developer, PRIV_DEVELOPER); 1094 CLPRIVCMD(founder, PRIV_CREATOR); 1095 CLCOMMANDM(priv, "si", (char *who, int *priv), intret(haspriv(d, clamp(*priv, 0, PRIV_MAX-1)) ? 1 : 0)); 1096 getclientversion(int cn,int prop)1097 void getclientversion(int cn, int prop) 1098 { 1099 gameent *d = cn >= 0 ? game::getclient(cn) : game::player1; 1100 if(d) switch(prop) 1101 { 1102 case -3: 1103 { 1104 defformatstring(branch, "%s", d->version.branch); 1105 if(d->version.build > 0) concformatstring(branch, "-%d", d->version.build); 1106 defformatstring(str, "%d.%d.%d-%s%d-%s", d->version.major, d->version.minor, d->version.patch, plat_name(d->version.platform), d->version.arch, branch); 1107 result(str); 1108 } 1109 case -2: result(plat_name(d->version.platform)); break; 1110 case -1: intret(16); 1111 case 0: intret(d->version.major); break; 1112 case 1: intret(d->version.minor); break; 1113 case 2: intret(d->version.patch); break; 1114 case 3: intret(d->version.game); break; 1115 case 4: intret(d->version.platform); break; 1116 case 5: intret(d->version.arch); break; 1117 case 6: intret(d->version.gpuglver); break; 1118 case 7: intret(d->version.gpuglslver); break; 1119 case 8: intret(d->version.crc); break; 1120 case 9: result(d->version.gpuvendor); break; 1121 case 10: result(d->version.gpurenderer); break; 1122 case 11: result(d->version.gpuversion); break; 1123 case 13: result(d->version.branch); break; 1124 case 14: intret(d->version.build); break; 1125 case 15: result(d->version.revision); break; 1126 default: break; 1127 } 1128 } 1129 ICOMMAND(IDF_NAMECOMPLETE, getclientversion, "si", (char *who, int *prop), getclientversion(parseplayer(who), *prop)); 1130 isspectator(int cn)1131 bool isspectator(int cn) 1132 { 1133 gameent *d = game::getclient(cn); 1134 return d && d->state == CS_SPECTATOR; 1135 } 1136 ICOMMAND(IDF_NAMECOMPLETE, isspectator, "s", (char *who), intret(isspectator(parseplayer(who)) ? 1 : 0)); 1137 isquarantine(int cn)1138 bool isquarantine(int cn) 1139 { 1140 gameent *d = game::getclient(cn); 1141 return d && d->quarantine; 1142 } 1143 ICOMMAND(IDF_NAMECOMPLETE, isquarantine, "s", (char *who), intret(isquarantine(parseplayer(who)) ? 1 : 0)); 1144 isai(int cn,int type)1145 bool isai(int cn, int type) 1146 { 1147 gameent *d = game::getclient(cn); 1148 int actortype = type > 0 && type < A_MAX ? type : A_BOT; 1149 return d && d->actortype == actortype; 1150 } 1151 ICOMMAND(IDF_NAMECOMPLETE, isai, "si", (char *who, int *type), intret(isai(parseplayer(who), *type) ? 1 : 0)); 1152 mutscmp(int req,int limit)1153 bool mutscmp(int req, int limit) 1154 { 1155 if(req) 1156 { 1157 if(!limit) return false; 1158 loopi(G_M_NUM) if(req&(1<<i) && !(limit&(1<<i))) return false; 1159 } 1160 return true; 1161 } 1162 ismodelocked(int reqmode,int reqmuts,int askmuts=0,const char * reqmap=NULL)1163 bool ismodelocked(int reqmode, int reqmuts, int askmuts = 0, const char *reqmap = NULL) 1164 { 1165 reqmuts |= mutslockforce; 1166 if(!m_game(reqmode) || (m_local(reqmode) && remote)) return true; 1167 if(!reqmap || !*reqmap) reqmap = "<random>"; 1168 bool israndom = !strcmp(reqmap, "<random>"); 1169 if(G(votelock)) switch(G(votelocktype)) 1170 { 1171 case 1: if(!haspriv(game::player1, G(votelock))) return true; break; 1172 case 2: 1173 if(!israndom && !m_edit(reqmode)) 1174 { 1175 int n = listincludes(previousmaps, reqmap, strlen(reqmap)); 1176 if(n >= 0 && n < G(maphistory) && !haspriv(game::player1, G(votelock))) return true; 1177 } 1178 break; 1179 case 0: default: break; 1180 } 1181 modecheck(reqmode, reqmuts, askmuts); 1182 if(askmuts && !mutscmp(askmuts, reqmuts)) return true; 1183 if(G(modelock)) switch(G(modelocktype)) 1184 { 1185 case 1: if(!haspriv(game::player1, G(modelock))) return true; break; 1186 case 2: if((!((1<<reqmode)&G(modelockfilter)) || !mutscmp(reqmuts, G(mutslockfilter))) && !haspriv(game::player1, G(modelock))) return true; break; 1187 case 0: default: break; 1188 } 1189 if(!israndom && !m_edit(reqmode) && G(mapslock)) 1190 { 1191 char *list = NULL; 1192 switch(G(mapslocktype)) 1193 { 1194 case 1: 1195 { 1196 list = newstring(G(allowmaps)); 1197 mapcull(list, reqmode, reqmuts, otherclients(true), G(mapsfilter), true); 1198 break; 1199 } 1200 case 2: 1201 { 1202 maplist(list, reqmode, reqmuts, otherclients(true), G(mapsfilter), true); 1203 break; 1204 } 1205 case 0: default: break; 1206 } 1207 if(list) 1208 { 1209 if(listincludes(list, reqmap, strlen(reqmap)) < 0 && !haspriv(game::player1, G(modelock))) 1210 { 1211 DELETEA(list); 1212 return true; 1213 } 1214 DELETEA(list); 1215 } 1216 } 1217 return false; 1218 } 1219 ICOMMAND(0, ismodelocked, "iiis", (int *g, int *m, int *a, char *s), intret(ismodelocked(*g, *m, *a, s) ? 1 : 0)); 1220 getmastermode(int n)1221 void getmastermode(int n) 1222 { 1223 switch(n) 1224 { 1225 case 0: intret(mastermode); return; 1226 case 1: result(mastermodename(mastermode)); return; 1227 default: break; 1228 } 1229 intret(-1); 1230 } 1231 ICOMMAND(0, getmastermode, "i", (int *n), getmastermode(*n)); 1232 addcontrol(const char * arg,int type,const char * msg)1233 void addcontrol(const char *arg, int type, const char *msg) 1234 { 1235 int i = parseplayer(arg); 1236 if(i >= 0) addmsg(N_ADDCONTROL, "ri2s", i, type, msg); 1237 } 1238 ICOMMAND(IDF_NAMECOMPLETE, kick, "ss", (char *s, char *m), addcontrol(s, -1, m)); 1239 ICOMMAND(IDF_NAMECOMPLETE, allow, "ss", (char *s, char *m), addcontrol(s, ipinfo::ALLOW, m)); 1240 ICOMMAND(IDF_NAMECOMPLETE, ban, "ss", (char *s, char *m), addcontrol(s, ipinfo::BAN, m)); 1241 ICOMMAND(IDF_NAMECOMPLETE, mute, "ss", (char *s, char *m), addcontrol(s, ipinfo::MUTE, m)); 1242 ICOMMAND(IDF_NAMECOMPLETE, limit, "ss", (char *s, char *m), addcontrol(s, ipinfo::LIMIT, m)); 1243 ICOMMAND(IDF_NAMECOMPLETE, except, "ss", (char *s, char *m), addcontrol(s, ipinfo::EXCEPT, m)); 1244 1245 ICOMMAND(0, clearallows, "", (), addmsg(N_CLRCONTROL, "ri", ipinfo::ALLOW)); 1246 ICOMMAND(0, clearbans, "", (), addmsg(N_CLRCONTROL, "ri", ipinfo::BAN)); 1247 ICOMMAND(0, clearmutes, "", (), addmsg(N_CLRCONTROL, "ri", ipinfo::MUTE)); 1248 ICOMMAND(0, clearlimits, "", (), addmsg(N_CLRCONTROL, "ri", ipinfo::LIMIT)); 1249 ICOMMAND(0, clearexcepts, "", (), addmsg(N_CLRCONTROL, "ri", ipinfo::EXCEPT)); 1250 1251 vector<char *> ignores; ignore(int cn)1252 void ignore(int cn) 1253 { 1254 gameent *d = game::getclient(cn); 1255 if(!d || d == game::player1) return; 1256 if(!strcmp(d->hostip, "*")) 1257 { 1258 conoutft(CON_EVENT, "\frCannot ignore %s: host information is private", game::colourname(d)); 1259 return; 1260 } 1261 if(ignores.find(d->hostip) < 0) 1262 { 1263 conoutft(CON_EVENT, "\fyIgnoring: \fs%s\fS (\fs\fc%s\fS)", game::colourname(d), d->hostip); 1264 ignores.add(d->hostip); 1265 } 1266 else 1267 conoutft(CON_EVENT, "\frAlready ignoring: \fs%s\fS (\fs\fc%s\fS)", game::colourname(d), d->hostip); 1268 } 1269 unignore(int cn)1270 void unignore(int cn) 1271 { 1272 gameent *d = game::getclient(cn); 1273 if(!d) return; 1274 if(!strcmp(d->hostip, "*")) 1275 { 1276 conoutft(CON_EVENT, "\frCannot unignore %s: host information is private", game::colourname(d)); 1277 return; 1278 } 1279 if(ignores.find(d->hostip) >= 0) 1280 { 1281 conoutft(CON_EVENT, "\fyStopped ignoring: \fs%s\fS (\fs\fc%s\fS)", game::colourname(d), d->hostip); 1282 ignores.removeobj(d->hostip); 1283 } 1284 else 1285 conoutft(CON_EVENT, "\frYou are not ignoring: \fs%s\fS (\fs\fc%s\fS)", game::colourname(d), d->hostip); 1286 } 1287 isignored(int cn)1288 bool isignored(int cn) 1289 { 1290 gameent *d = game::getclient(cn); 1291 if(!d || !strcmp(d->hostip, "*")) return false; 1292 return ignores.find(d->hostip) >= 0; 1293 } 1294 1295 ICOMMAND(IDF_NAMECOMPLETE, ignore, "s", (char *arg), ignore(parseplayer(arg))); 1296 ICOMMAND(IDF_NAMECOMPLETE, unignore, "s", (char *arg), unignore(parseplayer(arg))); 1297 ICOMMAND(IDF_NAMECOMPLETE, isignored, "s", (char *arg), intret(isignored(parseplayer(arg)) ? 1 : 0)); 1298 setteam(const char * arg1,const char * arg2)1299 void setteam(const char *arg1, const char *arg2) 1300 { 1301 if(m_team(game::gamemode, game::mutators)) 1302 { 1303 int i = parseplayer(arg1); 1304 if(i >= 0) 1305 { 1306 int t = teamfromname(arg2); 1307 if(t) addmsg(N_SETTEAM, "ri2", i, t); 1308 } 1309 } 1310 else conoutft(CON_DEBUG, "\frCan only change teams in team games"); 1311 } 1312 ICOMMAND(IDF_NAMECOMPLETE, setteam, "ss", (char *who, char *team), setteam(who, team)); 1313 hashpwd(const char * pwd)1314 void hashpwd(const char *pwd) 1315 { 1316 if(game::player1->clientnum < 0) return; 1317 string hash; 1318 server::hashpassword(game::player1->clientnum, sessionid, pwd, hash); 1319 result(hash); 1320 } 1321 COMMAND(0, hashpwd, "s"); 1322 setpriv(const char * arg)1323 void setpriv(const char *arg) 1324 { 1325 if(!arg[0]) return; 1326 int val = 1; 1327 stringz(hash); 1328 if(!arg[1] && isdigit(arg[0])) val = parseint(arg); 1329 else server::hashpassword(game::player1->clientnum, sessionid, arg, hash); 1330 addmsg(N_SETPRIV, "ris", val, hash); 1331 } 1332 COMMAND(0, setpriv, "s"); 1333 addpriv(int cn,int priv)1334 void addpriv(int cn, int priv) 1335 { 1336 addmsg(N_ADDPRIV, "ri2", cn, priv); 1337 } 1338 ICOMMAND(IDF_NAMECOMPLETE, addpriv, "si", (char *who, int *priv), addpriv(parseplayer(who), *priv)); 1339 ICOMMAND(IDF_NAMECOMPLETE, resetpriv, "s", (char *who), addpriv(parseplayer(who), -1)); 1340 tryauth()1341 void tryauth() 1342 { 1343 if(accountname[0]) addmsg(N_AUTHTRY, "rs", accountname); 1344 else conoutft(CON_DEBUG, "\frNo account set for \fcauth"); 1345 } 1346 ICOMMAND(0, auth, "", (), tryauth()); 1347 togglespectator(int cn,int val)1348 void togglespectator(int cn, int val) 1349 { 1350 if(cn >= 0) addmsg(N_SPECTATOR, "ri2", cn, val); 1351 } 1352 ICOMMAND(IDF_NAMECOMPLETE, spectator, "si", (char *who, int *val), togglespectator(parseplayer(who), *val)); 1353 ICOMMAND(0, spectate, "i", (int *val), togglespectator(game::player1->clientnum, *val)); 1354 connectattempt(const char * name,int port,const char * password,const ENetAddress & address)1355 void connectattempt(const char *name, int port, const char *password, const ENetAddress &address) 1356 { 1357 if(*password) { copystring(connectpass, password); } 1358 else memset(connectpass, 0, sizeof(connectpass)); 1359 } 1360 connectfail()1361 void connectfail() 1362 { 1363 memset(connectpass, 0, sizeof(connectpass)); 1364 } 1365 gameconnect(bool _remote)1366 void gameconnect(bool _remote) 1367 { 1368 remote = _remote; 1369 if(editmode) toggleedit(); 1370 } 1371 gamedisconnect(int clean)1372 void gamedisconnect(int clean) 1373 { 1374 if(editmode) toggleedit(); 1375 remote = isready = sendplayerinfo = sendgameinfo = sendcrcinfo = loadedmap = false; 1376 gettingmap = needsmap = sessionid = sessionver = lastplayerinfo = mastermode = 0; 1377 messages.shrink(0); 1378 mapvotes.shrink(0); 1379 messagereliable = false; 1380 projs::removeplayer(game::player1); 1381 removetrackedparticles(game::player1); 1382 removetrackedsounds(game::player1); 1383 game::player1->clientnum = -1; 1384 game::player1->privilege = PRIV_NONE; 1385 game::player1->handle[0] = game::player1->steamid[0] = '\0'; 1386 game::gamemode = G_EDITMODE; 1387 game::mutators = game::maptime = 0; 1388 loopv(game::players) if(game::players[i]) game::clientdisconnected(i); 1389 game::waiting.setsize(0); 1390 hud::cleanup(); 1391 emptymap(0, true, NULL, false); 1392 smartmusic(true); 1393 enumerate(idents, ident, id, 1394 { 1395 if(id.flags&IDF_CLIENT) switch(id.type) 1396 { 1397 case ID_VAR: setvar(id.name, id.def.i, true); break; 1398 case ID_FVAR: setfvar(id.name, id.def.f, true); break; 1399 case ID_SVAR: setsvar(id.name, *id.def.s ? id.def.s : "", true); break; 1400 default: break; 1401 } 1402 }); 1403 } 1404 addmsg(int type,const char * fmt,...)1405 bool addmsg(int type, const char *fmt, ...) 1406 { 1407 static uchar buf[MAXTRANS]; 1408 ucharbuf p(buf, MAXTRANS); 1409 putint(p, type); 1410 int numi = 1, nums = 0; 1411 bool reliable = false; 1412 if(fmt) 1413 { 1414 va_list args; 1415 va_start(args, fmt); 1416 while(*fmt) switch(*fmt++) 1417 { 1418 case 'r': reliable = true; break; 1419 case 'v': 1420 { 1421 int n = va_arg(args, int); 1422 int *v = va_arg(args, int *); 1423 loopi(n) putint(p, v[i]); 1424 numi += n; 1425 break; 1426 } 1427 case 'i': 1428 { 1429 int n = isdigit(*fmt) ? *fmt++-'0' : 1; 1430 loopi(n) putint(p, va_arg(args, int)); 1431 numi += n; 1432 break; 1433 } 1434 case 'u': 1435 { 1436 int n = isdigit(*fmt) ? *fmt++-'0' : 1; 1437 loopi(n) putuint(p, va_arg(args, uint)); 1438 numi += n; 1439 break; 1440 } 1441 case 'f': 1442 { 1443 int n = isdigit(*fmt) ? *fmt++-'0' : 1; 1444 loopi(n) putfloat(p, (float)va_arg(args, double)); 1445 numi += n; 1446 break; 1447 } 1448 case 's': sendstring(va_arg(args, const char *), p); nums++; break; 1449 case 'm': 1450 { 1451 int n = va_arg(args, int); 1452 p.put(va_arg(args, uchar *), n); 1453 numi += n; 1454 break; 1455 } 1456 } 1457 va_end(args); 1458 } 1459 int num = nums?0:numi, msgsize = msgsizelookup(type); 1460 if(msgsize && num != msgsize) { fatal("Inconsistent msg size for %d (%d != %d)", type, num, msgsize); } 1461 if(reliable) messagereliable = true; 1462 messages.put(buf, p.length()); 1463 return true; 1464 } 1465 saytext(gameent * f,gameent * t,int flags,char * text)1466 void saytext(gameent *f, gameent *t, int flags, char *text) 1467 { 1468 bigstring msg, line; 1469 filterstring(msg, text, true, colourchat ? false : true, true, true); 1470 if(*filterwords) filterword(msg, filterwords); 1471 1472 defformatstring(name, "%s", game::colourname(f)); 1473 if(flags&SAY_WHISPER) 1474 { 1475 if(!t) return; 1476 defformatstring(sw, " [\fs\fy%d\fS] (\fs\fcwhispers to %s\fS [\fs\fy%d\fS])", f->clientnum, t == game::player1 ? "you" : game::colourname(t), t->clientnum); 1477 concatstring(name, sw); 1478 } 1479 else if(flags&SAY_TEAM) 1480 { 1481 defformatstring(st, " (to team %s)", game::colourteam(f->team)); 1482 concatstring(name, st); 1483 } 1484 if(flags&SAY_ACTION) formatstring(line, "\fv* %s %s", name, msg); 1485 else formatstring(line, "\fw<%s> %s", name, msg); 1486 1487 int snd = S_CHAT; 1488 ident *wid = idents.access(flags&SAY_ACTION ? "on_action" : "on_text"); 1489 if(wid && wid->type == ID_ALIAS && wid->getstr()[0]) 1490 { 1491 defformatbigstring(act, "%s %d %d %s %s %s", 1492 flags&SAY_ACTION ? "on_action" : "on_text", f->clientnum, flags&SAY_TEAM ? 1 : 0, 1493 escapestring(game::colourname(f)), escapestring(text), escapestring(line)); 1494 int ret = execute(act); 1495 if(ret > 0) snd = ret; 1496 } 1497 if(m_demo(game::gamemode) || ((!(flags&SAY_TEAM) || f->team == game::player1->team) && (!(flags&SAY_WHISPER) || f == game::player1 || t == game::player1))) 1498 { 1499 conoutft(CON_MESG, "%s", line); 1500 if(snd >= 0 && !issound(f->cschan)) playsound(snd, f->o, f, snd != S_CHAT ? 0 : SND_DIRECT, -1, -1, -1, &f->cschan); 1501 } 1502 ai::scanchat(f, t, flags, text); 1503 } 1504 toserver(int flags,const char * text,const char * target)1505 void toserver(int flags, const char *text, const char *target) 1506 { 1507 if(!waiting(false) && !client::demoplayback) 1508 { 1509 bigstring output; 1510 copystring(output, text, messagelength); 1511 if(flags&SAY_WHISPER) 1512 { 1513 gameent *e = game::getclient(parseplayer(target)); 1514 if(e && e->clientnum != game::player1->clientnum) 1515 addmsg(N_TEXT, "ri3s", game::player1->clientnum, e->clientnum, flags, output); 1516 } 1517 else 1518 { 1519 if(flags&SAY_TEAM && !m_team(game::gamemode, game::mutators)) 1520 flags &= ~SAY_TEAM; 1521 addmsg(N_TEXT, "ri3s", game::player1->clientnum, -1, flags, output); 1522 } 1523 } 1524 } 1525 ICOMMAND(0, say, "C", (char *s), toserver(SAY_NONE, s)); 1526 ICOMMAND(0, me, "C", (char *s), toserver(SAY_ACTION, s)); 1527 ICOMMAND(0, sayteam, "C", (char *s), toserver(SAY_TEAM, s)); 1528 ICOMMAND(0, meteam, "C", (char *s), toserver(SAY_ACTION|SAY_TEAM, s)); 1529 ICOMMAND(IDF_NAMECOMPLETE, whisper, "ss", (char *t, char *s), toserver(SAY_WHISPER, s, t)); 1530 ICOMMAND(IDF_NAMECOMPLETE, mewhisper, "ss", (char *t, char *s), toserver(SAY_ACTION|SAY_WHISPER, s, t)); 1531 parsecommand(gameent * d,const char * cmd,const char * arg)1532 void parsecommand(gameent *d, const char *cmd, const char *arg) 1533 { 1534 const char *oldval = NULL; 1535 bool needfreeoldval = false; 1536 ident *id = idents.access(cmd); 1537 if(id && id->flags&IDF_CLIENT) 1538 { 1539 const char *val = NULL; 1540 switch(id->type) 1541 { 1542 case ID_COMMAND: 1543 { 1544 #if 0 // these shouldn't get here 1545 int slen = strlen(cmd)+1+strlen(arg); 1546 char *s = newstring(slen+1); 1547 formatstring(s, slen, "%s %s", cmd, arg); 1548 char *ret = executestr(s); 1549 delete[] s; 1550 if(ret) conoutft(CON_EVENT, "\fg%s: \fc%s", cmd, ret); 1551 delete[] ret; 1552 #endif 1553 return; 1554 } 1555 case ID_VAR: 1556 { 1557 int ret = parseint(arg); 1558 oldval = intstr(id); 1559 *id->storage.i = ret; 1560 id->changed(); 1561 val = intstr(id); 1562 break; 1563 } 1564 case ID_FVAR: 1565 { 1566 float ret = parsefloat(arg); 1567 oldval = floatstr(*id->storage.f); 1568 *id->storage.f = ret; 1569 id->changed(); 1570 val = floatstr(*id->storage.f); 1571 break; 1572 } 1573 case ID_SVAR: 1574 { 1575 oldval = newstring(*id->storage.s); 1576 needfreeoldval = true; 1577 delete[] *id->storage.s; 1578 *id->storage.s = newstring(arg); 1579 id->changed(); 1580 val = *id->storage.s; 1581 break; 1582 } 1583 default: return; 1584 } 1585 if((d || showservervariables) && val) 1586 { 1587 if(oldval) 1588 { 1589 conoutft(CON_EVENT, "\fy%s set \fs\fc%s\fS to \fs\fc%s\fS (was: \fs\fc%s\fS)", d ? game::colourname(d) : (connected(false) ? "the server" : "you"), cmd, val, oldval); 1590 if(needfreeoldval) delete[] oldval; 1591 } 1592 else conoutft(CON_EVENT, "\fy%s set \fs\fc%s\fS to \fs\fc%s\fS", d ? game::colourname(d) : (connected(false) ? "the server" : "you"), cmd, val); 1593 } 1594 } 1595 else if(verbose) conoutft(CON_EVENT, "\fr%s sent unknown command: \fc%s", d ? game::colourname(d) : "the server", cmd); 1596 } 1597 sendcmd(int nargs,const char * cmd,const char * arg)1598 bool sendcmd(int nargs, const char *cmd, const char *arg) 1599 { 1600 if(connected(false)) 1601 { 1602 addmsg(N_COMMAND, "ri2sis", game::player1->clientnum, nargs, cmd, strlen(arg), arg); 1603 return true; 1604 } 1605 else 1606 { 1607 defformatstring(scmd, "sv_%s", cmd); 1608 if(server::servcmd(nargs, scmd, arg)) 1609 { 1610 if(nargs > 1 && arg) parsecommand(NULL, cmd, arg); 1611 return true; 1612 } 1613 } 1614 return false; 1615 } 1616 changemapserv(char * name,int gamemode,int mutators,int crc,int variant)1617 void changemapserv(char *name, int gamemode, int mutators, int crc, int variant) 1618 { 1619 game::gamestate = G_S_WAITING; 1620 game::gamemode = gamemode; 1621 game::mutators = mutators; 1622 modecheck(game::gamemode, game::mutators); 1623 game::nextmode = game::gamemode; 1624 game::nextmuts = game::mutators; 1625 game::timeremaining = -1; 1626 game::maptime = 0; 1627 hud::resetscores(); 1628 mapvotes.shrink(0); 1629 if(editmode) toggleedit(); 1630 if(m_demo(game::gamemode)) 1631 { 1632 game::maptime = 1; 1633 game::timeremaining = 0; 1634 return; 1635 } 1636 else if(demoendless) demoendless = 0; 1637 if(m_capture(game::gamemode)) capture::reset(); 1638 else if(m_defend(game::gamemode)) defend::reset(); 1639 else if(m_bomber(game::gamemode)) bomber::reset(); 1640 needsmap = gettingmap = 0; 1641 smartmusic(true); 1642 if(crc < -1 || !name || !*name || !load_world(name, crc, variant)) switch(crc) 1643 { 1644 case -1: 1645 if(!mapcrc) emptymap(0, true, name); 1646 needsmap = gettingmap = 0; 1647 loadedmap = sendcrcinfo = true; // the server wants us to start 1648 break; 1649 case -2: 1650 conoutf("Waiting for server to request the map.."); 1651 default: 1652 emptymap(0, true, name); 1653 needsmap = totalmillis; 1654 if(crc > 0) addmsg(N_GETMAP, "r"); 1655 break; 1656 } 1657 else loadedmap = sendcrcinfo = true; 1658 if(m_capture(game::gamemode)) capture::setup(); 1659 else if(m_defend(game::gamemode)) defend::setup(); 1660 else if(m_bomber(game::gamemode)) bomber::setup(); 1661 } 1662 receivefile(uchar * data,int len)1663 void receivefile(uchar *data, int len) 1664 { 1665 ucharbuf p(data, len); 1666 int type = getint(p); 1667 switch(type) 1668 { 1669 case N_SENDDEMO: 1670 { 1671 int ctime = getint(p); 1672 int nameid = getint(p); 1673 if(filetimelocal) ctime += clockoffset; 1674 data += p.length(); 1675 len -= p.length(); 1676 string fname; 1677 const char *demoname = demonames.find(nameid, ""); 1678 if(*demoname) 1679 { 1680 formatstring(fname, "demos/%s.dmo", demoname); 1681 DELETEA(demoname); 1682 } 1683 else 1684 { 1685 if(*filetimeformat) formatstring(fname, "demos/%s.dmo", gettime(ctime, filetimeformat)); 1686 else formatstring(fname, "demos/%u.dmo", uint(ctime)); 1687 } 1688 stream *demo = openfile(fname, "wb"); 1689 if(!demo) return; 1690 conoutft(CON_EVENT, "\fyReceived demo: \fc%s", fname); 1691 demo->write(data, len); 1692 delete demo; 1693 break; 1694 } 1695 1696 case N_SENDMAPFILE: 1697 { 1698 int filetype = getint(p), filecrc = getint(p); 1699 string fname; 1700 gettingmap = totalmillis; 1701 getstring(fname, p); 1702 data += p.length(); 1703 len -= p.length(); 1704 if(filetype < 0 || filetype >= SENDMAP_MAX || len <= 0) break; 1705 if(!*fname) copystring(fname, "maps/untitled"); 1706 // Remove any temp/ prefix from file name before testing and rebuilding. 1707 defformatstring(nfname, "%s", (strstr(fname, "temp/") == fname || strstr(fname, "temp\\") == fname) ? fname + 5 : fname); 1708 defformatstring(ffile, strstr(nfname, "maps/") == nfname || strstr(nfname, "maps\\") == nfname ? "temp/%s_0x%.8x" : "temp/maps/%s_0x%.8x", nfname, filecrc); 1709 defformatstring(ffext, "%s.%s", ffile, sendmaptypes[filetype]); 1710 stream *f = openfile(ffext, "wb"); 1711 if(!f) 1712 { 1713 conoutft(CON_EVENT, "\frFailed to open map file: \fc%s", ffext); 1714 break; 1715 } 1716 f->write(data, len); 1717 delete f; 1718 conoutft(CON_EVENT, "\fyWrote map file: \fc%s (%d %s) [0x%.8x]", ffext, len, len != 1 ? "bytes" : "byte", filecrc); 1719 break; 1720 } 1721 default: break; 1722 } 1723 } 1724 ICOMMAND(0, getmap, "", (), if(multiplayer(false)) addmsg(N_GETMAP, "r")); 1725 stopdemo()1726 void stopdemo() 1727 { 1728 if(remote) addmsg(N_STOPDEMO, "r"); 1729 else server::stopdemo(); 1730 } 1731 ICOMMAND(0, stopdemo, "", (), stopdemo()); 1732 recorddemo(int val)1733 void recorddemo(int val) 1734 { 1735 addmsg(N_RECORDDEMO, "ri", val); 1736 } 1737 ICOMMAND(0, recorddemo, "i", (int *val), recorddemo(*val)); 1738 cleardemos(int val)1739 void cleardemos(int val) 1740 { 1741 addmsg(N_CLEARDEMOS, "ri", val); 1742 } 1743 ICOMMAND(0, cleardemos, "i", (int *val), cleardemos(*val)); 1744 getdemo(int i,const char * name)1745 void getdemo(int i, const char *name) 1746 { 1747 if(i <= 0) conoutft(CON_EVENT, "\fyGetting demo, please wait..."); 1748 else conoutft(CON_EVENT, "\fyGetting demo \fs\fc%d\fS, please wait...", i); 1749 addmsg(N_GETDEMO, "ri2", i, demonameid); 1750 if(*name) demonames.access(demonameid, newstring(name)); 1751 demonameid++; 1752 } 1753 ICOMMAND(0, getdemo, "is", (int *val, char *name), getdemo(*val, name)); 1754 listdemos()1755 void listdemos() 1756 { 1757 conoutft(CON_EVENT, "\fyListing demos..."); 1758 addmsg(N_LISTDEMOS, "r"); 1759 } 1760 ICOMMAND(0, listdemos, "", (), listdemos()); 1761 changemap(const char * name)1762 void changemap(const char *name) // request map change, server may ignore 1763 { 1764 int nextmode = game::nextmode, nextmuts = game::nextmuts; // in case stopdemo clobbers these 1765 if(!remote) stopdemo(); 1766 if(!connected()) 1767 { 1768 server::changemap(name, nextmode, nextmuts); 1769 localconnect(true); 1770 } 1771 else 1772 { 1773 stringz(reqfile); 1774 if(name && *name) 1775 copystring(reqfile, !strncasecmp(name, "temp/", 5) || !strncasecmp(name, "temp\\", 5) ? name+5 : name); 1776 else copystring(reqfile, "<random>"); 1777 addmsg(N_MAPVOTE, "rsi2", reqfile, nextmode, nextmuts); 1778 } 1779 } 1780 ICOMMAND(0, map, "s", (char *s), changemap(s)); 1781 ICOMMAND(0, clearvote, "", (), addmsg(N_CLEARVOTE, "r")); 1782 sendmap()1783 void sendmap() 1784 { 1785 conoutf("\fySending map..."); 1786 const char *reqmap = mapname; 1787 if(!reqmap || !*reqmap) reqmap = "maps/untitled"; 1788 bool saved = false; 1789 if(m_edit(game::gamemode)) 1790 { 1791 save_world(mapname, m_edit(game::gamemode), true); 1792 ai::savewaypoints(true, mapname); 1793 reqmap = mapname; 1794 saved = true; 1795 } 1796 loopi(SENDMAP_MAX) 1797 { 1798 defformatstring(reqfext, "%s.%s", reqmap, sendmaptypes[i]); 1799 stream *f = openfile(reqfext, "rb"); 1800 if(f) 1801 { 1802 conoutf("\fyTransmitting file: \fc%s", reqfext); 1803 sendfile(-1, 2, f, "ri3", N_SENDMAPFILE, i, mapcrc); 1804 if(needclipboard >= 0) needclipboard++; 1805 delete f; 1806 } 1807 else 1808 { 1809 conoutf("\frFailed to open map file: \fc%s", reqfext); 1810 sendfile(-1, 2, NULL, "ri3", N_SENDMAPFILE, i, mapcrc); 1811 } 1812 } 1813 if(saved) setnames(mapname); 1814 } 1815 ICOMMAND(0, sendmap, "", (), if(multiplayer(false)) sendmap()); 1816 gotoplayer(const char * arg)1817 void gotoplayer(const char *arg) 1818 { 1819 if(game::player1->state != CS_SPECTATOR && game::player1->state != CS_EDITING) return; 1820 int i = parseplayer(arg); 1821 if(i >= 0 && i != game::player1->clientnum) 1822 { 1823 gameent *d = game::getclient(i); 1824 if(!d) return; 1825 game::player1->o = d->o; 1826 vec dir(game::player1->yaw, game::player1->pitch); 1827 game::player1->o.add(dir.mul(-32)); 1828 game::player1->resetinterp(true); 1829 game::resetcamera(); 1830 } 1831 } 1832 ICOMMAND(IDF_NAMECOMPLETE, goto, "s", (char *s), gotoplayer(s)); 1833 editvar(ident * id,bool local)1834 void editvar(ident *id, bool local) 1835 { 1836 if(id && id->flags&IDF_WORLD && !(id->flags&IDF_SERVER) && local && m_edit(game::gamemode) && game::player1->state == CS_EDITING) 1837 { 1838 switch(id->type) 1839 { 1840 case ID_VAR: 1841 addmsg(N_EDITVAR, "risi", id->type, id->name, *id->storage.i); 1842 conoutft(CON_EVENT, "\fy%s set world variable \fs\fc%s\fS to \fs\fc%s\fS", game::colourname(game::player1), id->name, intstr(id)); 1843 break; 1844 case ID_FVAR: 1845 addmsg(N_EDITVAR, "risf", id->type, id->name, *id->storage.f); 1846 conoutft(CON_EVENT, "\fy%s set world variable \fs\fc%s\fS to \fs\fc%s\fS", game::colourname(game::player1), id->name, floatstr(*id->storage.f)); 1847 break; 1848 case ID_SVAR: 1849 addmsg(N_EDITVAR, "risis", id->type, id->name, strlen(*id->storage.s), *id->storage.s); 1850 conoutft(CON_EVENT, "\fy%s set world variable \fs\fc%s\fS to \fy\fc%s\fS", game::colourname(game::player1), id->name, *id->storage.s); 1851 break; 1852 case ID_ALIAS: 1853 { 1854 const char *s = id->getstr(); 1855 addmsg(N_EDITVAR, "risis", id->type, id->name, strlen(s), s); 1856 conoutft(CON_EVENT, "\fy%s set world alias \fs\fc%s\fS to \fs\fc%s\fS", game::colourname(game::player1), id->name, s); 1857 break; 1858 } 1859 default: break; 1860 } 1861 } 1862 } 1863 sendclipboard()1864 void sendclipboard() 1865 { 1866 uchar *outbuf = NULL; 1867 int inlen = 0, outlen = 0; 1868 if(!packeditinfo(localedit, inlen, outbuf, outlen)) 1869 { 1870 outbuf = NULL; 1871 inlen = outlen = 0; 1872 needclipboard = -1; 1873 } 1874 else needclipboard = 0; 1875 packetbuf p(16 + outlen, ENET_PACKET_FLAG_RELIABLE); 1876 putint(p, N_CLIPBOARD); 1877 putint(p, inlen); 1878 putint(p, outlen); 1879 if(outlen > 0) p.put(outbuf, outlen); 1880 sendclientpacket(p.finalize(), 1); 1881 } 1882 edittrigger(const selinfo & sel,int op,int arg1,int arg2,int arg3,const VSlot * vs)1883 void edittrigger(const selinfo &sel, int op, int arg1, int arg2, int arg3, const VSlot *vs) 1884 { 1885 if(m_edit(game::gamemode) && game::player1->state == CS_EDITING) switch(op) 1886 { 1887 case EDIT_FLIP: 1888 case EDIT_COPY: 1889 case EDIT_PASTE: 1890 case EDIT_DELCUBE: 1891 { 1892 switch(op) 1893 { 1894 case EDIT_COPY: needclipboard = arg1; break; // 0 - has clipboard; 1 - has clipboard with unknown geometry 1895 case EDIT_PASTE: 1896 if(needclipboard > 0) 1897 { 1898 c2sinfo(true); 1899 sendclipboard(); 1900 } 1901 break; 1902 } 1903 addmsg(N_EDITF + op, "ri9i4", 1904 sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, 1905 sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner); 1906 break; 1907 } 1908 case EDIT_MAT: 1909 { 1910 addmsg(N_EDITF + op, "ri9i7", 1911 sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, 1912 sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner, 1913 arg1, arg2, arg3); 1914 break; 1915 } 1916 case EDIT_ROTATE: 1917 { 1918 addmsg(N_EDITF + op, "ri9i5", 1919 sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, 1920 sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner, 1921 arg1); 1922 break; 1923 } 1924 case EDIT_FACE: 1925 { 1926 addmsg(N_EDITF + op, "ri9i6", 1927 sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, 1928 sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner, 1929 arg1, arg2); 1930 break; 1931 } 1932 case EDIT_TEX: 1933 { 1934 int tex1 = shouldpacktex(arg1); 1935 if(addmsg(N_EDITF + op, "ri9i6", 1936 sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, 1937 sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner, 1938 tex1 ? tex1 : arg1, arg2)) 1939 { 1940 messages.pad(2); 1941 int offset = messages.length(); 1942 if(tex1) packvslot(messages, arg1); 1943 *(ushort *)&messages[offset-2] = lilswap(ushort(messages.length() - offset)); 1944 } 1945 break; 1946 } 1947 case EDIT_REPLACE: 1948 { 1949 int tex1 = shouldpacktex(arg1), tex2 = shouldpacktex(arg2); 1950 if(addmsg(N_EDITF + op, "ri9i7", 1951 sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, 1952 sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner, 1953 tex1 ? tex1 : arg1, tex2 ? tex2 : arg2, arg3)) 1954 { 1955 messages.pad(2); 1956 int offset = messages.length(); 1957 if(tex1) packvslot(messages, arg1); 1958 if(tex2) packvslot(messages, arg2); 1959 *(ushort *)&messages[offset-2] = lilswap(ushort(messages.length() - offset)); 1960 } 1961 break; 1962 } 1963 case EDIT_CALCLIGHT: 1964 case EDIT_REMIP: 1965 { 1966 addmsg(N_EDITF + op, "r"); 1967 break; 1968 } 1969 case EDIT_VSLOT: 1970 { 1971 if(addmsg(N_EDITF + op, "ri9i6", 1972 sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, 1973 sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner, 1974 arg1, arg2)) 1975 { 1976 messages.pad(2); 1977 int offset = messages.length(); 1978 packvslot(messages, vs); 1979 *(ushort *)&messages[offset-2] = lilswap(ushort(messages.length() - offset)); 1980 } 1981 break; 1982 } 1983 case EDIT_UNDO: 1984 case EDIT_REDO: 1985 { 1986 uchar *outbuf = NULL; 1987 int inlen = 0, outlen = 0; 1988 if(packundo(op, inlen, outbuf, outlen)) 1989 { 1990 if(addmsg(N_EDITF + op, "ri2", inlen, outlen)) messages.put(outbuf, outlen); 1991 delete[] outbuf; 1992 } 1993 break; 1994 } 1995 } 1996 } 1997 sendintro()1998 void sendintro() 1999 { 2000 packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); 2001 2002 putint(p, N_CONNECT); 2003 2004 sendstring(game::player1->name, p); 2005 putint(p, game::player1->colour); 2006 putint(p, game::player1->model); 2007 putint(p, game::player1->pattern); 2008 sendstring(game::player1->vanity, p); 2009 putint(p, game::player1->loadweap.length()); 2010 loopv(game::player1->loadweap) putint(p, game::player1->loadweap[i]); 2011 putint(p, game::player1->randweap.length()); 2012 loopv(game::player1->randweap) putint(p, game::player1->randweap[i]); 2013 2014 stringz(hash); 2015 if(connectpass[0]) 2016 { 2017 server::hashpassword(game::player1->clientnum, sessionid, connectpass, hash); 2018 memset(connectpass, 0, sizeof(connectpass)); 2019 } 2020 sendstring(hash, p); 2021 sendstring(authconnect ? accountname : "", p); 2022 2023 game::player1->version.put(p); 2024 2025 sendclientpacket(p.finalize(), 1); 2026 } 2027 sendposition(gameent * d,packetbuf & q)2028 static void sendposition(gameent *d, packetbuf &q) 2029 { 2030 putint(q, N_POS); 2031 putuint(q, d->clientnum); 2032 // 3 bits phys state, 2 bits move, 2 bits strafe, 2 bits turnside 2033 uint physstate = d->physstate | ((d->move&3)<<3) | ((d->strafe&3)<<5) | ((d->turnside&3)<<7); 2034 putuint(q, physstate); 2035 putuint(q, d->impulse[IM_METER]); 2036 ivec o = ivec(vec(d->o.x, d->o.y, d->o.z-d->height).mul(DMF)), f = ivec(vec(d->floorpos.x, d->floorpos.y, d->floorpos.z).mul(DMF)); 2037 uint vel = min(int(d->vel.magnitude()*DVELF), 0xFFFF), fall = min(int(d->falling.magnitude()*DVELF), 0xFFFF); 2038 // 3 bits position, 3 bits floor, 1 bit velocity, 3 bits falling, 1 bit conopen, X bits actions 2039 uint flags = 0; 2040 if(o.x < 0 || o.x > 0xFFFF) flags |= 1<<0; 2041 if(o.y < 0 || o.y > 0xFFFF) flags |= 1<<1; 2042 if(o.z < 0 || o.z > 0xFFFF) flags |= 1<<2; 2043 if(f.x < 0 || f.x > 0xFFFF) flags |= 1<<3; 2044 if(f.y < 0 || f.y > 0xFFFF) flags |= 1<<4; 2045 if(f.z < 0 || f.z > 0xFFFF) flags |= 1<<5; 2046 if(vel > 0xFF) flags |= 1<<6; 2047 if(fall > 0) 2048 { 2049 flags |= 1<<7; 2050 if(fall > 0xFF) flags |= 1<<8; 2051 if(d->falling.x || d->falling.y || d->falling.z > 0) flags |= 1<<9; 2052 } 2053 if(d->conopen) flags |= 1<<10; 2054 if(d->forcepos) 2055 { 2056 flags |= 1<<11; 2057 d->forcepos = false; 2058 } 2059 loopk(AC_MAX) if(d->action[k]) flags |= 1<<(12+k); 2060 putuint(q, flags); 2061 loopk(3) 2062 { 2063 q.put(o[k]&0xFF); 2064 q.put((o[k]>>8)&0xFF); 2065 if(o[k] < 0 || o[k] > 0xFFFF) q.put((o[k]>>16)&0xFF); 2066 } 2067 loopk(3) 2068 { 2069 q.put(f[k]&0xFF); 2070 q.put((f[k]>>8)&0xFF); 2071 if(f[k] < 0 || f[k] > 0xFFFF) q.put((f[k]>>16)&0xFF); 2072 } 2073 float yaw = d->yaw, pitch = d->pitch; 2074 if(d == game::player1 && game::thirdpersonview(true, d)) 2075 { 2076 vectoyawpitch(vec(worldpos).sub(d->headpos()).normalize(), yaw, pitch); 2077 game::fixrange(yaw, pitch); 2078 } 2079 uint dir = (yaw < 0 ? 360 + int(yaw)%360 : int(yaw)%360) + clamp(int(pitch+90), 0, 180)*360; 2080 q.put(dir&0xFF); 2081 q.put((dir>>8)&0xFF); 2082 q.put(clamp(int(d->roll+90), 0, 180)); 2083 q.put(vel&0xFF); 2084 if(vel > 0xFF) q.put((vel>>8)&0xFF); 2085 float velyaw, velpitch; 2086 vectoyawpitch(d->vel, velyaw, velpitch); 2087 uint veldir = (velyaw < 0 ? 360 + int(velyaw)%360 : int(velyaw)%360) + clamp(int(velpitch+90), 0, 180)*360; 2088 q.put(veldir&0xFF); 2089 q.put((veldir>>8)&0xFF); 2090 if(fall > 0) 2091 { 2092 q.put(fall&0xFF); 2093 if(fall > 0xFF) q.put((fall>>8)&0xFF); 2094 if(d->falling.x || d->falling.y || d->falling.z > 0) 2095 { 2096 float fallyaw, fallpitch; 2097 vectoyawpitch(d->falling, fallyaw, fallpitch); 2098 uint falldir = (fallyaw < 0 ? 360 + int(fallyaw)%360 : int(fallyaw)%360) + clamp(int(fallpitch+90), 0, 180)*360; 2099 q.put(falldir&0xFF); 2100 q.put((falldir>>8)&0xFF); 2101 } 2102 } 2103 } 2104 sendpositions()2105 void sendpositions() 2106 { 2107 gameent *d = NULL; 2108 int numdyns = game::numdynents(); 2109 loopi(numdyns) if((d = (gameent *)game::iterdynents(i))) 2110 { 2111 if((d == game::player1 || d->ai) && (d->state == CS_ALIVE || d->state == CS_EDITING)) 2112 { 2113 packetbuf q(100); 2114 sendposition(d, q); 2115 for(int j = i+1; j < numdyns; j++) 2116 { 2117 gameent *e = (gameent *)game::iterdynents(j); 2118 if(e && (e == game::player1 || e->ai) && (e->state == CS_ALIVE || e->state == CS_EDITING)) 2119 sendposition(e, q); 2120 } 2121 sendclientpacket(q.finalize(), 0); 2122 break; 2123 } 2124 } 2125 } 2126 sendmessages()2127 void sendmessages() 2128 { 2129 packetbuf p(MAXTRANS); 2130 if(isready) 2131 { 2132 if(sendplayerinfo && (!lastplayerinfo || totalmillis-lastplayerinfo >= setinfowait)) 2133 { 2134 p.reliable(); 2135 sendplayerinfo = false; 2136 lastplayerinfo = totalmillis ? totalmillis : 1; 2137 putint(p, N_SETPLAYERINFO); 2138 sendstring(game::player1->name, p); 2139 putint(p, game::player1->colour); 2140 putint(p, game::player1->model); 2141 putint(p, game::player1->pattern); 2142 putint(p, game::player1->checkpointspawn); 2143 sendstring(game::player1->vanity, p); 2144 putint(p, game::player1->loadweap.length()); 2145 loopv(game::player1->loadweap) putint(p, game::player1->loadweap[i]); 2146 putint(p, game::player1->randweap.length()); 2147 loopv(game::player1->randweap) putint(p, game::player1->randweap[i]); 2148 } 2149 if(sendcrcinfo) 2150 { 2151 p.reliable(); 2152 putint(p, N_MAPCRC); 2153 sendstring(mapname, p); 2154 putint(p, mapcrc); 2155 sendcrcinfo = false; 2156 } 2157 if(sendgameinfo) 2158 { 2159 p.reliable(); 2160 putint(p, N_GAMEINFO); 2161 enumerate(idents, ident, id, 2162 { 2163 if(id.flags&IDF_CLIENT && id.flags&IDF_WORLD) switch(id.type) 2164 { 2165 case ID_VAR: 2166 putint(p, id.type); 2167 sendstring(id.name, p); 2168 putint(p, *id.storage.i); 2169 break; 2170 case ID_FVAR: 2171 putint(p, id.type); 2172 sendstring(id.name, p); 2173 putfloat(p, *id.storage.f); 2174 break; 2175 case ID_SVAR: 2176 putint(p, id.type); 2177 sendstring(id.name, p); 2178 sendstring(*id.storage.s, p); 2179 break; 2180 default: break; 2181 } 2182 }); 2183 putint(p, -1); 2184 entities::putitems(p); 2185 putint(p, -1); 2186 if(m_capture(game::gamemode)) capture::sendaffinity(p); 2187 else if(m_defend(game::gamemode)) defend::sendaffinity(p); 2188 else if(m_bomber(game::gamemode)) bomber::sendaffinity(p); 2189 sendgameinfo = false; 2190 } 2191 if(gs_playing(game::gamestate) && needsmap && !gettingmap && totalmillis-needsmap >= 30000) 2192 { 2193 p.reliable(); 2194 putint(p, N_GETMAP); 2195 needsmap = totalmillis; 2196 } 2197 } 2198 if(messages.length()) 2199 { 2200 p.put(messages.getbuf(), messages.length()); 2201 messages.setsize(0); 2202 if(messagereliable) p.reliable(); 2203 messagereliable = false; 2204 } 2205 if(totalmillis-lastping>250) 2206 { 2207 putint(p, N_PING); 2208 putint(p, totalmillis); 2209 lastping = totalmillis ? totalmillis : 1; 2210 } 2211 2212 sendclientpacket(p.finalize(), 1); 2213 } 2214 c2sinfo(bool force)2215 void c2sinfo(bool force) // send update to the server 2216 { 2217 static int lastupdate = -1000; 2218 if(totalmillis-lastupdate < 40 && !force) return; // don't update faster than 25fps 2219 lastupdate = totalmillis ? totalmillis : 1; 2220 sendpositions(); 2221 sendmessages(); 2222 flushclient(); 2223 } 2224 parsestate(gameent * d,ucharbuf & p,bool resume=false)2225 bool parsestate(gameent *d, ucharbuf &p, bool resume = false) 2226 { 2227 if(!d) { static gameent dummy; d = &dummy; } 2228 bool local = d == game::player1 || d->ai, reset = false; 2229 if(!local || !resume) d->respawn(lastmillis, game::gamemode, game::mutators); 2230 int state = getint(p); 2231 if(state == -1) reset = true; 2232 else if(!local) d->state = state; 2233 d->points = getint(p); 2234 d->frags = getint(p); 2235 d->deaths = getint(p); 2236 d->totalpoints = getint(p); 2237 d->totalfrags = getint(p); 2238 d->totaldeaths = getint(p); 2239 d->totalavgpos = getfloat(p); 2240 d->timeplayed = getint(p); 2241 d->lasttimeplayed = totalmillis ? totalmillis : 1; 2242 d->health = getint(p); 2243 d->cptime = getint(p); 2244 if(local && resume && !reset) 2245 { 2246 d->weapreset(false); 2247 getint(p); 2248 loopi(W_MAX) loopj(W_A_MAX) getint(p); 2249 loopi(W_MAX) getint(p); 2250 } 2251 else 2252 { 2253 d->weapreset(!resume); 2254 int weap = getint(p); 2255 d->weapselect = isweap(weap) ? weap : W_CLAW; 2256 loopi(W_MAX) loopj(W_A_MAX) d->weapammo[i][j] = getint(p); 2257 loopi(W_MAX) d->weapent[i] = getint(p); 2258 } 2259 if(resume) d->configure(lastmillis, game::gamemode, game::mutators, physics::carryaffinity(d)); 2260 return reset; 2261 } 2262 updatepos(gameent * d)2263 void updatepos(gameent *d) 2264 { 2265 // update the position of other clients in the game in our world 2266 // don't care if he's in the scenery or other players, just don't overlap with our client 2267 const float r = game::player1->radius+d->radius; 2268 const float dx = game::player1->o.x-d->o.x; 2269 const float dy = game::player1->o.y-d->o.y; 2270 const float dz = game::player1->o.z-d->o.z; 2271 const float rz = game::player1->aboveeye+game::player1->height; 2272 const float fx = (float)fabs(dx), fy = (float)fabs(dy), fz = (float)fabs(dz); 2273 if(fx < r && fy < r && fz < rz && d->state != CS_SPECTATOR && d->state != CS_WAITING && d->state != CS_DEAD) 2274 { 2275 if(fx < fy) d->o.y += dy < 0 ? r-fy : -(r-fy); // push aside 2276 else d->o.x += dx < 0 ? r-fx : -(r-fx); 2277 } 2278 int lagtime = totalmillis-d->lastupdate; 2279 if(lagtime) 2280 { 2281 if(d->lastupdate) d->plag = (d->plag*5+lagtime)/6; 2282 d->lastupdate = totalmillis ? totalmillis : 1; 2283 } 2284 } 2285 parsepositions(ucharbuf & p)2286 void parsepositions(ucharbuf &p) 2287 { 2288 int type; 2289 while(p.remaining()) switch(type = getint(p)) 2290 { 2291 case N_POS: // position of another client 2292 { 2293 int lcn = getuint(p), physstate = getuint(p), meter = getuint(p), flags = getuint(p); 2294 vec o, f, vel, falling; 2295 float yaw, pitch, roll; 2296 loopk(3) 2297 { 2298 int n = p.get(); 2299 n |= p.get()<<8; 2300 if(flags&(1<<k)) 2301 { 2302 n |= p.get()<<16; 2303 if(n&0x800000) n |= ~0U<<24; 2304 } 2305 o[k] = n/DMF; 2306 } 2307 loopk(3) 2308 { 2309 int n = p.get(); 2310 n |= p.get()<<8; 2311 if(flags&(1<<(k+3))) 2312 { 2313 n |= p.get()<<16; 2314 if(n&0x800000) n |= ~0U<<24; 2315 } 2316 f[k] = n/DMF; 2317 } 2318 int dir = p.get(); 2319 dir |= p.get()<<8; 2320 yaw = dir%360; 2321 pitch = clamp(dir/360, 0, 180)-90; 2322 roll = clamp(int(p.get()), 0, 180)-90; 2323 int mag = p.get(); 2324 if(flags&(1<<6)) mag |= p.get()<<8; 2325 dir = p.get(); 2326 dir |= p.get()<<8; 2327 vecfromyawpitch(dir%360, clamp(dir/360, 0, 180)-90, 1, 0, vel); 2328 vel.mul(mag/DVELF); 2329 if(flags&(1<<7)) 2330 { 2331 mag = p.get(); 2332 if(flags&(1<<8)) mag |= p.get()<<8; 2333 if(flags&(1<<9)) 2334 { 2335 dir = p.get(); 2336 dir |= p.get()<<8; 2337 vecfromyawpitch(dir%360, clamp(dir/360, 0, 180)-90, 1, 0, falling); 2338 } 2339 else falling = vec(0, 0, -1); 2340 falling.mul(mag/DVELF); 2341 } 2342 else falling = vec(0, 0, 0); 2343 gameent *d = game::getclient(lcn); 2344 if(!d || d == game::player1 || d->ai) continue; 2345 float oldyaw = d->yaw, oldpitch = d->pitch; 2346 d->yaw = yaw; 2347 d->pitch = pitch; 2348 d->roll = roll; 2349 d->move = (physstate>>3)&2 ? -1 : (physstate>>3)&1; 2350 d->strafe = (physstate>>5)&2 ? -1 : (physstate>>5)&1; 2351 d->turnside = (physstate>>7)&2 ? -1 : (physstate>>7)&1; 2352 d->impulse[IM_METER] = meter; 2353 d->conopen = flags&(1<<10) ? true : false; 2354 d->forcepos = flags&(1<<11) ? true : false; 2355 loopk(AC_MAX) 2356 { 2357 bool val = d->action[k]; 2358 d->action[k] = flags&(1<<(12+k)) ? true : false; 2359 if(val != d->action[k]) 2360 { 2361 if(d->action[k]) d->actiontime[k] = lastmillis; 2362 else if(k == AC_CROUCH || k == AC_JUMP) d->actiontime[k] = -lastmillis; 2363 } 2364 } 2365 vec oldpos(d->o); 2366 d->o = o; 2367 d->o.z += d->height; 2368 d->floorpos = f; 2369 d->vel = vel; 2370 d->falling = falling; 2371 d->physstate = physstate&7; 2372 physics::updatephysstate(d); 2373 updatepos(d); 2374 if(d->forcepos || d->state == CS_DEAD || d->state == CS_WAITING) 2375 { 2376 d->resetinterp(); 2377 d->smoothmillis = 0; 2378 d->forcepos = false; 2379 } 2380 else if(d->respawned < 0) 2381 { 2382 d->resetinterp(); 2383 d->smoothmillis = 0; 2384 game::respawned(d, false); 2385 d->respawned = lastmillis; 2386 } 2387 else if(physics::smoothmove && d->smoothmillis >= 0 && oldpos.dist(d->o) < physics::smoothdist) 2388 { 2389 d->newpos = d->o; 2390 d->newpos.z -= d->height; 2391 d->newyaw = d->yaw; 2392 d->newpitch = d->pitch; 2393 2394 d->o = oldpos; 2395 d->yaw = oldyaw; 2396 d->pitch = oldpitch; 2397 2398 oldpos.z -= d->height; 2399 (d->deltapos = oldpos).sub(d->newpos); 2400 2401 d->deltayaw = oldyaw - d->newyaw; 2402 if(d->deltayaw > 180) d->deltayaw -= 360; 2403 else if(d->deltayaw < -180) d->deltayaw += 360; 2404 d->deltapitch = oldpitch - d->newpitch; 2405 2406 d->smoothmillis = lastmillis; 2407 } 2408 else d->smoothmillis = 0; 2409 break; 2410 } 2411 2412 default: 2413 neterr("type"); 2414 return; 2415 } 2416 } 2417 parsemessages(int cn,gameent * d,ucharbuf & p)2418 void parsemessages(int cn, gameent *d, ucharbuf &p) 2419 { 2420 static char text[MAXTRANS]; 2421 int type = -1, prevtype = -1; 2422 2423 while(p.remaining()) 2424 { 2425 prevtype = type; 2426 type = getint(p); 2427 if(debugmessages) conoutf("[client] msg: %d, prev: %d", type, prevtype); 2428 switch(type) 2429 { 2430 case N_SERVERINIT: // welcome messsage from the server 2431 { 2432 game::player1->clientnum = getint(p); 2433 sessionver = getint(p); 2434 getstring(game::player1->hostip, p); 2435 sessionid = getint(p); 2436 if(sessionver != VERSION_GAME) 2437 { 2438 conoutft(CON_EVENT, "\frError: this server is running an incompatible protocol (%d v %d)", sessionver, VERSION_GAME); 2439 disconnect(); 2440 return; 2441 } 2442 sessionflags = getint(p); 2443 conoutf("Connected, starting negotiation with server"); 2444 sendintro(); 2445 break; 2446 } 2447 case N_WELCOME: 2448 mastermode = getint(p); 2449 conoutf("Negotiation complete, \fs\fcmastermode\fS is \fs\fc%d\fS (\fs\fc%s\fS)", mastermode, mastermodename(mastermode)); 2450 isready = true; 2451 break; 2452 2453 case N_CLIENT: 2454 { 2455 int lcn = getint(p), len = getuint(p); 2456 ucharbuf q = p.subbuf(len); 2457 gameent *t = game::getclient(lcn); 2458 parsemessages(lcn, t, q); 2459 break; 2460 } 2461 2462 case N_SPHY: // simple phys events 2463 { 2464 int lcn = getint(p), st = getint(p); 2465 gameent *t = game::getclient(lcn); 2466 bool proceed = t && (st >= SPHY_SERVER || (t != game::player1 && !t->ai)); 2467 switch(st) 2468 { 2469 case SPHY_JUMP: 2470 { 2471 if(!proceed) break; 2472 t->doimpulse(IM_T_JUMP, lastmillis); 2473 playsound(S_JUMP, t->o, t); 2474 createshape(PART_SMOKE, int(t->radius), 0x222222, 21, 20, 250, t->feetpos(), 1, 1, -10, 0, 10.f); 2475 break; 2476 } 2477 case SPHY_BOOST: case SPHY_POUND: case SPHY_SLIDE: case SPHY_MELEE: case SPHY_KICK: case SPHY_GRAB: case SPHY_PARKOUR: case SPHY_AFTER: 2478 { 2479 if(!proceed) break; 2480 t->doimpulse(IM_T_BOOST+(st-SPHY_BOOST), lastmillis); 2481 game::impulseeffect(t); 2482 if(st == SPHY_KICK || st == SPHY_PARKOUR || st == SPHY_MELEE) game::footstep(d); 2483 break; 2484 } 2485 case SPHY_EXTINGUISH: 2486 { 2487 if(!proceed) break; 2488 t->resetresidual(W_R_BURN); 2489 playsound(S_EXTINGUISH, t->o, t); 2490 part_create(PART_SMOKE, 500, t->feetpos(t->height/2), 0xAAAAAA, t->radius*4, 1, -10); 2491 break; 2492 } 2493 case SPHY_BUFF: 2494 { 2495 int param = getint(p); 2496 if(!proceed) break; 2497 t->lastbuff = param ? lastmillis : 0; 2498 break; 2499 } 2500 default: break; 2501 } 2502 break; 2503 } 2504 2505 case N_ANNOUNCE: 2506 { 2507 int snd = getint(p), targ = getint(p); 2508 getstring(text, p); 2509 if(targ >= 0 && text[0]) game::announcef(snd, targ, NULL, false, "%s", text); 2510 else game::announce(snd); 2511 break; 2512 } 2513 2514 case N_TEXT: 2515 { 2516 int fcn = getint(p), tcn = getint(p), flags = getint(p); 2517 getstring(text, p); 2518 gameent *fcp = game::getclient(fcn); 2519 gameent *tcp = game::getclient(tcn); 2520 if(!fcp || isignored(fcp->clientnum) || isignored(fcp->ownernum)) break; 2521 saytext(fcp, tcp, flags, text); 2522 break; 2523 } 2524 2525 case N_COMMAND: 2526 { 2527 int lcn = getint(p); 2528 gameent *f = lcn >= 0 ? game::getclient(lcn) : NULL; 2529 getstring(text, p); 2530 int alen = getint(p); 2531 if(alen < 0 || alen > p.remaining()) break; 2532 char *arg = newstring(alen); 2533 getstring(arg, p, alen+1); 2534 parsecommand(f, text, arg); 2535 delete[] arg; 2536 break; 2537 } 2538 2539 case N_EXECLINK: 2540 { 2541 int tcn = getint(p), index = getint(p); 2542 gameent *t = game::getclient(tcn); 2543 if(!t || !d || (t->clientnum != d->clientnum && t->ownernum != d->clientnum) || t == game::player1 || t->ai) break; 2544 entities::execlink(t, index, false); 2545 break; 2546 } 2547 2548 case N_MAPCHANGE: 2549 { 2550 getstring(text, p); 2551 int mode = getint(p), muts = getint(p), crc = getint(p), variant = clamp(getint(p), int(MPV_DEF), int(MPV_MAX-1)); 2552 if(crc >= 0) conoutf("Map change: %s (%d:%d) [0x%.8x] (%s)", text, mode, muts, crc, mapvariants[variant]); 2553 else conoutf("Map change: %s (%d:%d) [%d] (%s)", text, mode, muts, crc, mapvariants[variant]); 2554 changemapserv(text, mode, muts, crc, variant); 2555 break; 2556 } 2557 2558 case N_GETGAMEINFO: 2559 { 2560 conoutf("Sending game info.."); 2561 sendgameinfo = true; 2562 break; 2563 } 2564 2565 case N_GAMEINFO: 2566 { 2567 int n; 2568 sendgameinfo = false; 2569 while(p.remaining() && (n = getint(p)) != -1) entities::setspawn(n, getint(p)); 2570 break; 2571 } 2572 2573 case N_ATTRMAP: 2574 { 2575 loopi(W_MAX) game::attrmap[i] = getint(p); 2576 break; 2577 } 2578 2579 case N_SETPLAYERINFO: // name colour model pattern checkpoint vanity count <loadweaps> count <randweaps> 2580 { 2581 getstring(text, p); 2582 int colour = getint(p), model = getint(p), pattern = getint(p), cps = getint(p); 2583 stringz(vanity); 2584 getstring(vanity, p); 2585 int lw = getint(p); 2586 vector<int> lweaps; 2587 loopk(lw) lweaps.add(getint(p)); 2588 int rw = getint(p); 2589 vector<int> rweaps; 2590 loopk(rw) rweaps.add(getint(p)); 2591 if(!d) break; 2592 stringz(namestr); 2593 filterstring(namestr, text, true, true, true, true, MAXNAMELEN); 2594 if(!*namestr) copystring(namestr, "unnamed"); 2595 if(strcmp(d->name, namestr)) 2596 { 2597 string oldname, newname; 2598 copystring(oldname, game::colourname(d)); 2599 d->setinfo(namestr, colour, model, pattern, vanity, lweaps, rweaps); 2600 copystring(newname, game::colourname(d)); 2601 if(showpresence >= (waiting(false) ? 2 : 1) && !isignored(d->clientnum)) 2602 conoutft(CON_EVENT, "\fm%s is now known as %s", oldname, newname); 2603 } 2604 else d->setinfo(namestr, colour, model, pattern, vanity, lweaps, rweaps); 2605 d->checkpointspawn = cps; 2606 break; 2607 } 2608 2609 case N_CLIENTINIT: // cn colour model pattern checkpoint team priv name vanity count <loadweaps> count <randweaps> handle steamid hostip <version> 2610 { 2611 int tcn = getint(p); 2612 verinfo dummy; 2613 gameent *d = game::newclient(tcn); 2614 if(!d) 2615 { 2616 loopk(6) getint(p); 2617 loopk(2) getstring(text, p); 2618 loopj(2) 2619 { 2620 int w = getint(p); 2621 loopk(w) getint(p); 2622 } 2623 loopk(3) getstring(text, p); 2624 dummy.get(p); 2625 break; 2626 } 2627 int colour = getint(p), model = getint(p), pattern = getint(p), cps = getint(p), team = clamp(getint(p), int(T_NEUTRAL), int(T_ENEMY)), priv = getint(p); 2628 getstring(text, p); 2629 stringz(namestr); 2630 filterstring(namestr, text, true, true, true, true, MAXNAMELEN); 2631 if(!*namestr) copystring(namestr, "unnamed"); 2632 stringz(vanity); 2633 getstring(vanity, p); 2634 int lw = getint(p); 2635 vector<int> lweaps; 2636 loopk(lw) lweaps.add(getint(p)); 2637 int rw = getint(p); 2638 vector<int> rweaps; 2639 loopk(rw) rweaps.add(getint(p)); 2640 getstring(d->handle, p); 2641 getstring(d->steamid, p); 2642 getstring(d->hostip, p); 2643 if(d != game::player1) d->version.get(p); 2644 else dummy.get(p); 2645 d->checkpointspawn = cps; 2646 if(d == game::focus && d->team != team) hud::lastteam = 0; 2647 d->team = team; 2648 d->privilege = priv; 2649 if(d->name[0]) d->setinfo(namestr, colour, model, pattern, vanity, lweaps, rweaps); // already connected 2650 else // new client 2651 { 2652 d->setinfo(namestr, colour, model, pattern, vanity, lweaps, rweaps); 2653 if(showpresence >= (waiting(false) ? 2 : 1)) 2654 { 2655 int amt = otherclients(true); 2656 stringz(ipaddr); 2657 if(showpresencehostinfo && client::haspriv(game::player1, G(iphostlock))) formatstring(ipaddr, " (%s)", d->hostip); 2658 if(priv > PRIV_NONE) 2659 { 2660 if(d->handle[0]) conoutft(CON_EVENT, "\fg%s%s joined the game (\fs\fy%s\fS: \fs\fc%s\fS) [%d.%d.%d-%s%d-%s] (%d %s)", game::colourname(d), ipaddr, server::privname(d->privilege), d->handle, d->version.major, d->version.minor, d->version.patch, plat_name(d->version.platform), d->version.arch, d->version.branch, amt, amt != 1 ? "players" : "player"); 2661 else conoutft(CON_EVENT, "\fg%s%s joined the game (\fs\fy%s\fS) [%d.%d.%d-%s%d-%s] (%d %s)", game::colourname(d), ipaddr, server::privname(d->privilege), d->version.major, d->version.minor, d->version.patch, plat_name(d->version.platform), d->version.arch, d->version.branch, amt, amt != 1 ? "players" : "player"); 2662 } 2663 else conoutft(CON_EVENT, "\fg%s%s joined the game [%d.%d.%d-%s%d-%s] (%d %s)", game::colourname(d), ipaddr, d->version.major, d->version.minor, d->version.patch, plat_name(d->version.platform), d->version.arch, d->version.branch, amt, amt != 1 ? "players" : "player"); 2664 } 2665 if(needclipboard >= 0) needclipboard++; 2666 game::specreset(d); 2667 } 2668 break; 2669 } 2670 2671 case N_DISCONNECT: 2672 { 2673 int lcn = getint(p), reason = getint(p); 2674 game::clientdisconnected(lcn, reason); 2675 break; 2676 } 2677 2678 case N_LOADOUT: 2679 { 2680 hud::showscores(false); 2681 if(!UI::hasmenu()) UI::openui("profile"); 2682 lastplayerinfo = 0; 2683 break; 2684 } 2685 2686 case N_SPAWN: 2687 { 2688 int lcn = getint(p); 2689 gameent *f = game::newclient(lcn); 2690 if(!f || f == game::player1 || f->ai) f = NULL; 2691 parsestate(f, p); 2692 break; 2693 } 2694 2695 case N_SPAWNSTATE: 2696 { 2697 int lcn = getint(p), ent = getint(p); 2698 gameent *f = game::newclient(lcn); 2699 if(!f || (f != game::player1 && !f->ai)) 2700 { 2701 parsestate(NULL, p); 2702 break; 2703 } 2704 if(f == game::player1 && editmode) toggleedit(); 2705 parsestate(f, p); 2706 game::respawned(f, true, ent); 2707 break; 2708 } 2709 2710 case N_SHOTFX: 2711 { 2712 int scn = getint(p), weap = getint(p), flags = getint(p), len = getint(p), target = getint(p); 2713 vec from, dest; 2714 loopk(3) from[k] = getint(p)/DMF; 2715 loopk(3) dest[k] = getint(p)/DMF; 2716 int ls = getint(p); 2717 vector<shotmsg> shots; 2718 loopj(ls) 2719 { 2720 shotmsg &s = shots.add(); 2721 s.id = getint(p); 2722 loopk(3) s.pos[k] = getint(p); 2723 } 2724 gameent *t = game::getclient(scn), *v = game::getclient(target); 2725 if(!t || !isweap(weap) || t == game::player1 || t->ai) break; 2726 if(weap != t->weapselect) t->weapswitch(weap, lastmillis); 2727 float scale = 1; 2728 int sub = W2(weap, ammosub, WS(flags)); 2729 if(W2(weap, cooktime, WS(flags))) 2730 { 2731 scale = len/float(W2(weap, cooktime, WS(flags))); 2732 if(sub > 1) sub = int(ceilf(sub*scale)); 2733 } 2734 projs::shootv(weap, flags, sub, 0, scale, from, dest, shots, t, false, v); 2735 break; 2736 } 2737 2738 case N_DESTROY: 2739 { 2740 int scn = getint(p), targ = getint(p), idx = getint(p), num = idx < 0 ? 0-idx : idx; 2741 gameent *t = game::getclient(scn); 2742 loopi(num) 2743 { 2744 int id = getint(p); 2745 if(t) projs::destruct(t, targ, id, idx < 0); 2746 } 2747 break; 2748 } 2749 2750 case N_STICKY: // cn target id norm pos 2751 { 2752 int scn = getint(p), tcn = getint(p), id = getint(p); 2753 vec norm(0, 0, 0), pos(0, 0, 0); 2754 loopk(3) norm[k] = getint(p)/DNF; 2755 loopk(3) pos[k] = getint(p)/DMF; 2756 gameent *t = game::getclient(scn), *v = tcn >= 0 ? game::getclient(tcn) : NULL; 2757 if(t && (tcn < 0 || v)) projs::sticky(t, id, norm, pos, v); 2758 break; 2759 } 2760 2761 case N_DAMAGE: 2762 { 2763 int tcn = getint(p), acn = getint(p), weap = getint(p), flags = getint(p), damage = getint(p), health = getint(p); 2764 vec dir, vel; 2765 loopk(3) dir[k] = getint(p)/DNF; 2766 loopk(3) vel[k] = getint(p)/DNF; 2767 dir.normalize(); 2768 float dist = getint(p)/DNF; 2769 gameent *m = game::getclient(tcn), *v = game::getclient(acn); 2770 if(!m || !v) break; 2771 game::damaged(weap, flags, damage, health, m, v, lastmillis, dir, vel, dist); 2772 break; 2773 } 2774 2775 case N_BURNRES: 2776 { 2777 int cn = getint(p); 2778 gameent *m = game::getclient(cn); 2779 if(!m) break; 2780 m->burntime = getint(p); 2781 m->burndelay = getint(p); 2782 m->burndamage = getint(p); 2783 break; 2784 } 2785 2786 case N_BLEEDRES: 2787 { 2788 int cn = getint(p); 2789 gameent *m = game::getclient(cn); 2790 if(!m) break; 2791 m->bleedtime = getint(p); 2792 m->bleeddelay = getint(p); 2793 m->bleeddamage = getint(p); 2794 break; 2795 } 2796 2797 case N_SHOCKRES: 2798 { 2799 int cn = getint(p); 2800 gameent *m = game::getclient(cn); 2801 if(!m) break; 2802 m->shocktime = getint(p); 2803 m->shockdelay = getint(p); 2804 m->shockdamage = getint(p); 2805 m->shockstun = getint(p); 2806 m->shockstunscale = getfloat(p); 2807 m->shockstunfall = getfloat(p); 2808 m->shockstuntime = getint(p); 2809 break; 2810 } 2811 2812 case N_RELOAD: 2813 { 2814 int trg = getint(p), weap = getint(p), amt = getint(p), ammo = getint(p), store = getint(p); 2815 gameent *m = game::getclient(trg); 2816 if(!m || !isweap(weap)) break; 2817 weapons::weapreload(m, weap, amt, ammo, store, false); 2818 break; 2819 } 2820 2821 case N_REGEN: 2822 { 2823 int trg = getint(p), heal = getint(p), amt = getint(p); 2824 gameent *f = game::getclient(trg); 2825 if(!f) break; 2826 if(!amt) 2827 { 2828 f->impulse[IM_METER] = 0; 2829 f->resetresidual(); 2830 } 2831 else if(amt > 0 && (!f->lastregen || lastmillis-f->lastregen >= 500)) playsound(S_REGEN, f->o, f); 2832 f->health = heal; 2833 f->lastregen = lastmillis; 2834 f->lastregenamt = amt; 2835 break; 2836 } 2837 2838 case N_DIED: 2839 { 2840 int vcn = getint(p), deaths = getint(p), tdeaths = getint(p), acn = getint(p), frags = getint(p), tfrags = getint(p), spree = getint(p), style = getint(p), weap = getint(p), flags = getint(p), damage = getint(p), material = getint(p); 2841 gameent *m = game::getclient(vcn), *v = game::getclient(acn); 2842 static vector<gameent *> assist; assist.setsize(0); 2843 int count = getint(p); 2844 loopi(count) 2845 { 2846 int lcn = getint(p); 2847 gameent *log = game::getclient(lcn); 2848 if(log) assist.add(log); 2849 } 2850 if(!v || !m) break; 2851 m->deaths = deaths; 2852 m->totaldeaths = tdeaths; 2853 v->frags = frags; 2854 v->totalfrags = tfrags; 2855 v->spree = spree; 2856 game::killed(weap, flags, damage, m, v, assist, style, material); 2857 m->lastdeath = lastmillis; 2858 m->weapreset(true); 2859 break; 2860 } 2861 2862 case N_POINTS: 2863 { 2864 int acn = getint(p), add = getint(p), points = getint(p), total = getint(p); 2865 gameent *v = game::getclient(acn); 2866 if(!v) break; 2867 v->lastpoints = add; 2868 v->points = points; 2869 v->totalpoints = total; 2870 break; 2871 } 2872 2873 case N_TOTALS: 2874 { 2875 int acn = getint(p), totalp = getint(p), totalf = getint(p), totald = getint(p); 2876 float totalap = getfloat(p); 2877 gameent *v = game::getclient(acn); 2878 if(!v) break; 2879 v->totalpoints = totalp; 2880 v->totalfrags = totalf; 2881 v->totaldeaths = totald; 2882 v->totalavgpos = totalap; 2883 break; 2884 } 2885 2886 case N_AVGPOS: 2887 { 2888 int acn = getint(p); 2889 float totalap = getfloat(p); 2890 gameent *v = game::getclient(acn); 2891 if(!v) break; 2892 v->totalavgpos = totalap; 2893 break; 2894 } 2895 2896 case N_WEAPDROP: 2897 { 2898 int trg = getint(p), weap = getint(p), ds = getint(p); 2899 gameent *m = game::getclient(trg); 2900 bool local = m && (m == game::player1 || m->ai); 2901 if(ds) loopj(ds) 2902 { 2903 int gs = getint(p), drop = getint(p), ammo = getint(p); 2904 if(m) projs::drop(m, gs, drop, ammo, local, j, weap); 2905 } 2906 if(isweap(weap) && m) 2907 { 2908 if(m->weapswitch(weap, lastmillis, W(weap, delayswitch))) 2909 playsound(WSND(weap, S_W_SWITCH), m->o, m, 0, -1, -1, -1, &m->wschan[WS_MAIN_CHAN]); 2910 } 2911 break; 2912 } 2913 2914 case N_WEAPSELECT: 2915 { 2916 int trg = getint(p), weap = getint(p); 2917 gameent *m = game::getclient(trg); 2918 if(!m || !isweap(weap)) break; 2919 weapons::weapselect(m, weap, (1<<W_S_SWITCH)|(1<<W_S_RELOAD), false); 2920 break; 2921 } 2922 2923 case N_WEAPCOOK: 2924 { 2925 int trg = getint(p), weap = getint(p), etype = getint(p), offtime = getint(p); 2926 gameent *m = game::getclient(trg); 2927 if(!m || !isweap(weap)) break; 2928 if(etype >= 0) 2929 { 2930 float maxscale = 1; 2931 int sub = W2(weap, ammosub, etype >= 1); 2932 if(sub > 1 && m->weapammo[weap][W_A_CLIP] < sub) maxscale = m->weapammo[weap][W_A_CLIP]/float(sub); 2933 m->setweapstate(weap, etype >= 2 ? W_S_ZOOM : W_S_POWER, max(int(W2(weap, cooktime, etype >= 1)*maxscale), 1), lastmillis, offtime); 2934 } 2935 else m->setweapstate(weap, W_S_IDLE, 0, lastmillis, 0, true); 2936 break; 2937 } 2938 2939 case N_RESUME: 2940 { 2941 for(;;) 2942 { 2943 int lcn = getint(p); 2944 if(p.overread() || lcn < 0) break; 2945 gameent *f = game::newclient(lcn); 2946 if(!f) parsestate(NULL, p); 2947 else if(parsestate(f, p, true)) 2948 addmsg(N_RESUME, "ri", f->clientnum); 2949 } 2950 break; 2951 } 2952 2953 case N_ITEMSPAWN: 2954 { 2955 int ent = getint(p), value = getint(p); 2956 if(!entities::ents.inrange(ent)) break; 2957 gameentity &e = *(gameentity *)entities::ents[ent]; 2958 entities::setspawn(ent, value); 2959 ai::itemspawned(ent, value!=0); 2960 if(e.spawned()) 2961 { 2962 int attr = m_attr(e.type, e.attrs[0]), colour = e.type == WEAPON && isweap(attr) ? W(attr, colour) : colourwhite; 2963 playsound(e.type == WEAPON && attr >= W_OFFSET && attr < W_ALL ? WSND(attr, S_W_SPAWN) : S_ITEMSPAWN, e.o, NULL, 0, -1, -1, -1, &e.schan); 2964 if(entities::showentdescs) 2965 { 2966 vec pos = vec(e.o).add(vec(0, 0, 4)); 2967 const char *texname = entities::showentdescs >= 2 ? hud::itemtex(e.type, attr) : NULL; 2968 if(texname && *texname) part_icon(pos, textureload(texname, 3), game::aboveitemiconsize, 1, -10, 0, game::eventiconfade, colour); 2969 else 2970 { 2971 const char *item = entities::entinfo(e.type, e.attrs, 0); 2972 if(item && *item) 2973 { 2974 defformatstring(ds, "<emphasis>%s", item); 2975 part_textcopy(pos, ds, PART_TEXT, game::eventiconfade, colourwhite, 2, 1, -10); 2976 } 2977 } 2978 } 2979 game::spawneffect(PART_SPARK, e.o, enttype[e.type].radius*0.25f, colour, 1); 2980 if(game::dynlighteffects) adddynlight(e.o, enttype[e.type].radius, vec::fromcolor(colour).mul(2.f), 250, 250); 2981 } 2982 break; 2983 } 2984 2985 case N_TRIGGER: 2986 { 2987 int ent = getint(p), st = getint(p); 2988 entities::setspawn(ent, st); 2989 break; 2990 } 2991 2992 case N_ITEMACC: 2993 { // uses a specific drop so the client knows what to replace 2994 int lcn = getint(p), fcn = getint(p), ent = getint(p), ammoamt = getint(p), spawn = getint(p), 2995 weap = getint(p), drop = getint(p), ammo = getint(p); 2996 gameent *m = game::getclient(lcn); 2997 if(!m) break; 2998 if(entities::ents.inrange(ent) && enttype[entities::ents[ent]->type].usetype == EU_ITEM) 2999 entities::useeffects(m, fcn, ent, ammoamt, spawn, weap, drop, ammo); 3000 break; 3001 } 3002 3003 case N_EDITVAR: 3004 { 3005 int t = getint(p); 3006 bool commit = true; 3007 getstring(text, p); 3008 ident *id = idents.access(text); 3009 if(!d || !id || !(id->flags&IDF_WORLD) || id->type != t) commit = false; 3010 switch(t) 3011 { 3012 case ID_VAR: 3013 { 3014 int val = getint(p); 3015 if(commit) 3016 { 3017 if(id->flags&IDF_HEX && uint(id->maxval) == 0xFFFFFFFFU) 3018 { 3019 if(uint(val) > uint(id->maxval)) val = uint(id->maxval); 3020 else if(uint(val) < uint(id->minval)) val = uint(id->minval); 3021 } 3022 else if(val > id->maxval) val = id->maxval; 3023 else if(val < id->minval) val = id->minval; 3024 setvar(text, val, true); 3025 conoutft(CON_EVENT, "\fy%s set world variable \fs\fc%s\fS to \fs\fc%s\fS", game::colourname(d), id->name, intstr(id)); 3026 } 3027 break; 3028 } 3029 case ID_FVAR: 3030 { 3031 float val = getfloat(p); 3032 if(commit) 3033 { 3034 if(val > id->maxvalf) val = id->maxvalf; 3035 else if(val < id->minvalf) val = id->minvalf; 3036 setfvar(text, val, true); 3037 conoutft(CON_EVENT, "\fy%s set world variable \fs\fc%s\fS to \fs\fc%s\fS", game::colourname(d), id->name, floatstr(*id->storage.f)); 3038 } 3039 break; 3040 } 3041 case ID_SVAR: 3042 { 3043 int vlen = getint(p); 3044 if(vlen < 0 || vlen > p.remaining()) break; 3045 char *val = newstring(vlen); 3046 getstring(val, p, vlen+1); 3047 if(commit) 3048 { 3049 setsvar(text, val, true); 3050 conoutft(CON_EVENT, "\fy%s set world variable \fs\fc%s\fS to \fy\fc%s\fS", game::colourname(d), id->name, *id->storage.s); 3051 } 3052 delete[] val; 3053 break; 3054 } 3055 case ID_ALIAS: 3056 { 3057 int vlen = getint(p); 3058 if(vlen < 0 || vlen > p.remaining()) break; 3059 char *val = newstring(vlen); 3060 getstring(val, p, vlen+1); 3061 if(commit || !id) // set aliases anyway 3062 { 3063 worldalias(text, val); 3064 conoutft(CON_EVENT, "\fy%s set world alias \fs\fc%s\fS to \fs\fc%s\fS", game::colourname(d), text, val); 3065 } 3066 delete[] val; 3067 break; 3068 } 3069 default: break; 3070 } 3071 break; 3072 } 3073 3074 case N_CLIPBOARD: 3075 { 3076 int tcn = getint(p), unpacklen = getint(p), packlen = getint(p); 3077 gameent *d = game::getclient(tcn); 3078 ucharbuf q = p.subbuf(max(packlen, 0)); 3079 if(d) unpackeditinfo(d->edit, q.buf, q.maxlen, unpacklen); 3080 break; 3081 } 3082 3083 case N_UNDO: 3084 case N_REDO: 3085 { 3086 int cn = getint(p), unpacklen = getint(p), packlen = getint(p); 3087 gameent *d = game::getclient(cn); 3088 ucharbuf q = p.subbuf(max(packlen, 0)); 3089 if(d) unpackundo(q.buf, q.maxlen, unpacklen); 3090 break; 3091 } 3092 3093 case N_EDITF: // coop editing messages 3094 case N_EDITT: 3095 case N_EDITM: 3096 case N_FLIP: 3097 case N_COPY: 3098 case N_PASTE: 3099 case N_ROTATE: 3100 case N_REPLACE: 3101 case N_DELCUBE: 3102 case N_EDITVSLOT: 3103 { 3104 if(!d) return; 3105 selinfo s; 3106 s.o.x = getint(p); s.o.y = getint(p); s.o.z = getint(p); 3107 s.s.x = getint(p); s.s.y = getint(p); s.s.z = getint(p); 3108 s.grid = getint(p); s.orient = getint(p); 3109 s.cx = getint(p); s.cxs = getint(p); s.cy = getint(p), s.cys = getint(p); 3110 s.corner = getint(p); 3111 switch(type) 3112 { 3113 case N_EDITF: { int dir = getint(p), mode = getint(p); if(s.validate()) mpeditface(dir, mode, s, false); break; } 3114 case N_EDITT: 3115 { 3116 int tex = getint(p), 3117 allfaces = getint(p); 3118 if(p.remaining() < 2) return; 3119 int extra = lilswap(*(const ushort *)p.pad(2)); 3120 if(p.remaining() < extra) return; 3121 ucharbuf ebuf = p.subbuf(extra); 3122 if(s.validate()) mpedittex(tex, allfaces, s, ebuf); 3123 break; 3124 } 3125 case N_EDITM: { int mat = getint(p), filter = getint(p), style = getint(p); if(s.validate()) mpeditmat(mat, filter, style, s, false); break; } 3126 case N_FLIP: if(s.validate()) mpflip(s, false); break; 3127 case N_COPY: if(d && s.validate()) mpcopy(d->edit, s, false); break; 3128 case N_PASTE: if(d && s.validate()) mppaste(d->edit, s, false); break; 3129 case N_ROTATE: { int dir = getint(p); if(s.validate()) mprotate(dir, s, false); break; } 3130 case N_REPLACE: 3131 { 3132 int oldtex = getint(p), 3133 newtex = getint(p), 3134 insel = getint(p); 3135 if(p.remaining() < 2) return; 3136 int extra = lilswap(*(const ushort *)p.pad(2)); 3137 if(p.remaining() < extra) return; 3138 ucharbuf ebuf = p.subbuf(extra); 3139 if(s.validate()) mpreplacetex(oldtex, newtex, insel>0, s, ebuf); 3140 break; 3141 } 3142 case N_DELCUBE: if(s.validate()) mpdelcube(s, false); break; 3143 case N_EDITVSLOT: 3144 { 3145 int delta = getint(p), 3146 allfaces = getint(p); 3147 if(p.remaining() < 2) return; 3148 int extra = lilswap(*(const ushort *)p.pad(2)); 3149 if(p.remaining() < extra) return; 3150 ucharbuf ebuf = p.subbuf(extra); 3151 if(s.validate()) mpeditvslot(delta, allfaces, s, ebuf); 3152 break; 3153 } 3154 } 3155 break; 3156 } 3157 case N_REMIP: 3158 { 3159 if(!d) return; 3160 conoutft(CON_EVENT, "\fy%s remipped", game::colourname(d)); 3161 mpremip(false); 3162 break; 3163 } 3164 case N_CALCLIGHT: 3165 if(!d) return; 3166 conoutf("\fy%s calced lights", game::colourname(d)); 3167 mpcalclight(false); 3168 break; 3169 case N_EDITENT: // coop edit of ent 3170 { 3171 if(!d) return; 3172 int i = getint(p); 3173 float x = getint(p)/DMF, y = getint(p)/DMF, z = getint(p)/DMF; 3174 int type = getint(p), numattrs = getint(p); 3175 attrvector attrs; 3176 attrs.add(0, clamp(numattrs, entities::numattrs(type), MAXENTATTRS)); 3177 loopk(numattrs) 3178 { 3179 int val = getint(p); 3180 if(attrs.inrange(k)) attrs[k] = val; 3181 } 3182 mpeditent(i, vec(x, y, z), type, attrs, false); 3183 entities::setspawn(i, 0); 3184 break; 3185 } 3186 3187 case N_EDITLINK: 3188 { 3189 if(!d) return; 3190 int b = getint(p), index = getint(p), node = getint(p); 3191 entities::linkents(index, node, b!=0, false, false); 3192 break; 3193 } 3194 3195 case N_PONG: 3196 addmsg(N_CLIENTPING, "i", game::player1->ping = (game::player1->ping*5+totalmillis-getint(p))/6); 3197 break; 3198 3199 case N_CLIENTPING: 3200 if(!d) return; 3201 d->ping = getint(p); 3202 loopv(game::players) if(game::players[i] && game::players[i]->ownernum == d->clientnum) 3203 game::players[i]->ping = d->ping; 3204 break; 3205 3206 case N_MASTERMODE: 3207 { 3208 int tcn = getint(p); 3209 mastermode = getint(p); 3210 if(tcn >= 0) 3211 { 3212 gameent *e = game::getclient(tcn); 3213 if(e) 3214 { 3215 conoutft(CON_EVENT, "\fy%s set \fs\fcmastermode\fS to \fs\fc%d\fS (\fs\fc%s\fS)", game::colourname(e), mastermode, mastermodename(mastermode)); 3216 break; 3217 } 3218 } 3219 conoutft(CON_EVENT, "\fyThe server set \fs\fcmastermode\fS to \fs\fc%d\fS (\fs\fc%s\fS)", mastermode, mastermodename(mastermode)); 3220 break; 3221 } 3222 3223 case N_TICK: 3224 { 3225 int state = getint(p), remain = getint(p); 3226 game::timeupdate(state, remain); 3227 break; 3228 } 3229 3230 case N_SERVMSG: 3231 { 3232 int lev = getint(p); 3233 getstring(text, p); 3234 conoutft(lev >= 0 && lev < CON_MAX ? lev : CON_DEBUG, "%s", text); 3235 break; 3236 } 3237 3238 case N_SENDDEMOLIST: 3239 { 3240 int demos = getint(p); 3241 if(demos <= 0) conoutft(CON_EVENT, "\foNo demos available"); 3242 else loopi(demos) 3243 { 3244 getstring(text, p); 3245 int len = getint(p), ctime = getint(p); 3246 if(p.overread()) break; 3247 conoutft(CON_EVENT, "\fyDemo: %2d. \fs\fc%s\fS recorded \fs\fc%s UTC\fS [\fs\fw%.2f%s\fS]", i+1, text, gettime(ctime, "%Y-%m-%d %H:%M.%S"), len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB"); 3248 } 3249 break; 3250 } 3251 3252 case N_DEMOPLAYBACK: 3253 { 3254 bool wasdemopb = demoplayback; 3255 demoplayback = getint(p)!=0; 3256 if(demoplayback) game::player1->state = CS_SPECTATOR; 3257 else loopv(game::players) if(game::players[i]) game::clientdisconnected(i); 3258 game::player1->clientnum = getint(p); 3259 if(!demoplayback && wasdemopb && demoendless) 3260 { 3261 stringz(demofile); 3262 if(*demolist) 3263 { 3264 int r = rnd(listlen(demolist)), len = 0; 3265 const char *elem = indexlist(demolist, r, len); 3266 if(len > 0) copystring(demofile, elem, len+1); 3267 } 3268 if(!*demofile) 3269 { 3270 vector<char *> files; 3271 listfiles("demos", "dmo", files); 3272 while(!files.empty()) 3273 { 3274 int r = rnd(files.length()); 3275 if(files[r][0] != '.') 3276 { 3277 copystring(demofile, files[r]); 3278 break; 3279 } 3280 else files.remove(r); 3281 } 3282 } 3283 if(*demofile) addmsg(N_MAPVOTE, "rsi2", demofile, G_DEMO, 0); 3284 } 3285 break; 3286 } 3287 3288 case N_DEMOREADY: 3289 { 3290 int num = getint(p), ctime = getint(p), len = getint(p); 3291 getstring(text, p); 3292 conoutft(CON_EVENT, "\fyDemo \fs\fc%s\fS recorded \fs\fc%s UTC\fS [\fs\fw%.2f%s\fS]", text, gettime(ctime, "%Y-%m-%d %H:%M.%S"), len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB"); 3293 if(demoautoclientsave) getdemo(num, ""); 3294 break; 3295 } 3296 3297 case N_CURRENTPRIV: 3298 { 3299 int mn = getint(p), priv = getint(p); 3300 getstring(text, p); 3301 gameent *m = game::getclient(mn); 3302 if(m) 3303 { 3304 m->privilege = priv; 3305 copystring(m->handle, text); 3306 } 3307 break; 3308 } 3309 3310 case N_EDITMODE: 3311 { 3312 int val = getint(p); 3313 if(!d) break; 3314 if(val) d->state = CS_EDITING; 3315 else 3316 { 3317 d->state = CS_ALIVE; 3318 d->editspawn(game::gamemode, game::mutators); 3319 } 3320 d->resetinterp(); 3321 projs::removeplayer(d); 3322 break; 3323 } 3324 3325 case N_SPECTATOR: 3326 { 3327 int sn = getint(p), val = getint(p); 3328 gameent *s = game::newclient(sn); 3329 if(!s) break; 3330 if(s == game::player1) 3331 { 3332 game::specreset(); 3333 if(m_race(game::gamemode)) game::specmode = 0; 3334 } 3335 if(val != 0) 3336 { 3337 if(s == game::player1 && editmode) toggleedit(); 3338 s->state = CS_SPECTATOR; 3339 s->quarantine = val == 2; 3340 s->respawned = -1; 3341 } 3342 else 3343 { 3344 if(s->state == CS_SPECTATOR) 3345 { 3346 s->state = CS_WAITING; 3347 if(s != game::player1 && !s->ai) s->resetinterp(); 3348 game::waiting.removeobj(s); 3349 } 3350 s->quarantine = false; 3351 } 3352 break; 3353 } 3354 3355 case N_WAITING: 3356 { 3357 int sn = getint(p); 3358 gameent *s = game::newclient(sn); 3359 if(!s) break; 3360 if(s == game::player1) 3361 { 3362 if(editmode) toggleedit(); 3363 hud::showscores(false); 3364 s->stopmoving(true); 3365 game::waiting.setsize(0); 3366 gameent *d; 3367 loopv(game::players) if((d = game::players[i]) && d->actortype == A_PLAYER && d->state == CS_WAITING) 3368 game::waiting.add(d); 3369 } 3370 else if(!s->ai) s->resetinterp(); 3371 game::waiting.removeobj(s); 3372 if(s->state == CS_ALIVE) s->lastdeath = lastmillis; // so spawn delay shows properly 3373 else entities::spawnplayer(s); // so they're not nowhere 3374 s->state = CS_WAITING; 3375 s->quarantine = false; 3376 s->weapreset(true); 3377 break; 3378 } 3379 3380 case N_SETTEAM: 3381 { 3382 int wn = getint(p), tn = getint(p); 3383 gameent *w = game::getclient(wn); 3384 if(!w) return; 3385 if(w->team != tn) 3386 { 3387 if(m_team(game::gamemode, game::mutators) && w->actortype == A_PLAYER && showteamchange >= (w->team != T_NEUTRAL && tn != T_NEUTRAL ? 1 : 2)) 3388 conoutft(CON_EVENT, "\fa%s is now on team %s", game::colourname(w), game::colourteam(tn)); 3389 w->team = tn; 3390 if(w == game::focus) hud::lastteam = 0; 3391 } 3392 break; 3393 } 3394 3395 case N_INFOAFFIN: 3396 { 3397 int flag = getint(p), converted = getint(p), 3398 owner = getint(p), enemy = getint(p); 3399 if(m_defend(game::gamemode)) defend::updateaffinity(flag, owner, enemy, converted); 3400 break; 3401 } 3402 3403 case N_SETUPAFFIN: 3404 { 3405 if(m_defend(game::gamemode)) defend::parseaffinity(p); 3406 break; 3407 } 3408 3409 case N_MAPVOTE: 3410 { 3411 int vn = getint(p); 3412 gameent *v = game::getclient(vn); 3413 getstring(text, p); 3414 filterstring(text, text); 3415 int reqmode = getint(p), reqmuts = getint(p); 3416 if(!v) break; 3417 vote(v, text, reqmode, reqmuts); 3418 break; 3419 } 3420 3421 case N_CLEARVOTE: 3422 { 3423 int vn = getint(p); 3424 gameent *v = game::getclient(vn); 3425 if(!v) break; 3426 clearvotes(v, true); 3427 break; 3428 } 3429 3430 case N_CHECKPOINT: 3431 { 3432 int tn = getint(p), ent = getint(p); 3433 gameent *t = game::getclient(tn); 3434 if(!t || !m_race(game::gamemode)) 3435 { 3436 if(ent < 0) break; 3437 if(getint(p) < 0) break; 3438 loopi(2) getint(p); 3439 break; 3440 } 3441 if(ent >= 0) 3442 { 3443 if(entities::ents.inrange(ent) && entities::ents[ent]->type == CHECKPOINT) 3444 { 3445 if(t != game::player1 && !t->ai && (!t->cpmillis || entities::ents[ent]->attrs[6] == CP_START)) t->cpmillis = lastmillis; 3446 if((checkpointannounce&(t != game::focus ? 2 : 1) || (m_ra_gauntlet(game::gamemode, game::mutators) && checkpointannounce&4)) && checkpointannouncefilter&(1<<entities::ents[ent]->attrs[6])) 3447 { 3448 switch(entities::ents[ent]->attrs[6]) 3449 { 3450 case CP_START: game::announce(S_V_START, t); break; 3451 case CP_FINISH: case CP_LAST: game::announce(S_V_COMPLETE, t); break; 3452 default: game::announce(S_V_CHECKPOINT, t); break; 3453 } 3454 } 3455 entities::execlink(t, ent, false); 3456 } 3457 int laptime = getint(p); 3458 if(laptime >= 0) 3459 { 3460 t->cplast = laptime; 3461 t->cptime = getint(p); 3462 t->points = getint(p); 3463 t->cpmillis = t->impulse[IM_METER] = 0; 3464 if(showlaptimes >= (t != game::focus ? (t->actortype > A_PLAYER ? 3 : 2) : 1)) 3465 { 3466 defformatstring(best, "%s", timestr(t->cptime, 1)); 3467 conoutft(CON_EVENT, "%s completed in \fs\fg%s\fS (best: \fs\fy%s\fS, laps: \fs\fc%d\fS)", game::colourname(t), timestr(t->cplast, 1), best, t->points); 3468 } 3469 } 3470 } 3471 else 3472 { 3473 t->checkpoint = -1; 3474 t->cpmillis = ent == -2 ? lastmillis : 0; 3475 } 3476 } 3477 3478 case N_SCORE: 3479 { 3480 int team = getint(p), total = getint(p); 3481 if(m_team(game::gamemode, game::mutators)) 3482 { 3483 score &ts = hud::teamscore(team); 3484 ts.total = total; 3485 } 3486 break; 3487 } 3488 3489 case N_DUELEND: 3490 { 3491 int winner = getint(p); // Round winner (-1 if everyone died, team in team modes, cn otherwise). 3492 if(winner == -1) 3493 { // If nobody won, just announce draw. 3494 game::announcef(S_V_DRAW, CON_EVENT, NULL, false, "\fyEveryone died, \fzoyEPIC FAIL!"); 3495 break; 3496 } 3497 int playing = getint(p); // Were we playing? 3498 if(playing >= 2) 3499 { 3500 defformatstring(msg, "\fyTeam %s are the winners", game::colourteam(winner)); 3501 // If we were playing, announce with appropriate sound for if we won or lost. 3502 if(playing == 3) game::announcef((winner == game::player1->team) ? S_V_YOUWIN : S_V_YOULOSE, CON_EVENT, NULL, false, "%s", msg); 3503 else game::announcef(S_V_BOMBSCORE, CON_EVENT, NULL, false, "%s", msg); 3504 } 3505 else 3506 { 3507 int wins = getint(p); // Winner's win streak. 3508 gameent *t = game::getclient(winner); 3509 if(!t) break; 3510 string msg, hp = ""; 3511 if(!m_insta(game::gamemode, game::mutators)) 3512 { // If applicable, create remaining health or flawless message. 3513 if(t->health >= t->gethealth(game::gamemode, game::mutators)) formatstring(hp, " with a \fs\fcflawless victory\fS"); 3514 else 3515 { 3516 if(game::damageinteger) formatstring(hp, " with \fs\fc%d\fS health left", int(ceilf(t->health/game::damagedivisor))); 3517 else formatstring(hp, " with \fs\fc%.1f\fS health left", t->health/game::damagedivisor); 3518 } 3519 } 3520 // Format win message with streak if it has more than one win. 3521 if(wins == 1) formatstring(msg, "\fy%s was the winner%s", game::colourname(t), hp); 3522 else formatstring(msg, "\fy%s was the winner%s (\fs\fc%d\fS in a row)", game::colourname(t), hp, wins); 3523 // If we were playing, announce with appropriate sound for if we won or lost. 3524 if(playing) game::announcef((winner == game::player1->clientnum) ? S_V_YOUWIN : S_V_YOULOSE, CON_EVENT, NULL, false, "%s", msg); 3525 else game::announcef(S_V_BOMBSCORE, CON_EVENT, NULL, false, "%s", msg); 3526 } 3527 break; 3528 } 3529 3530 case N_INITAFFIN: 3531 { 3532 if(m_capture(game::gamemode)) capture::parseaffinity(p); 3533 else if(m_bomber(game::gamemode)) bomber::parseaffinity(p); 3534 break; 3535 } 3536 3537 case N_DROPAFFIN: 3538 { 3539 int ocn = getint(p), otc = getint(p), flag = getint(p); 3540 vec droploc, inertia; 3541 loopk(3) droploc[k] = getint(p)/DMF; 3542 loopk(3) inertia[k] = getint(p)/DMF; 3543 gameent *o = game::newclient(ocn); 3544 if(o) 3545 { 3546 if(m_capture(game::gamemode)) capture::dropaffinity(o, flag, droploc, inertia, otc); 3547 else if(m_bomber(game::gamemode)) bomber::dropaffinity(o, flag, droploc, inertia, otc); 3548 } 3549 break; 3550 } 3551 3552 case N_SCOREAFFIN: 3553 { 3554 int ocn = getint(p), relayflag = getint(p), goalflag = getint(p), score = getint(p); 3555 gameent *o = game::newclient(ocn); 3556 if(o) 3557 { 3558 if(m_capture(game::gamemode)) capture::scoreaffinity(o, relayflag, goalflag, score); 3559 else if(m_bomber(game::gamemode)) bomber::scoreaffinity(o, relayflag, goalflag, score); 3560 } 3561 break; 3562 } 3563 3564 case N_RETURNAFFIN: 3565 { 3566 int ocn = getint(p), flag = getint(p); 3567 gameent *o = game::newclient(ocn); 3568 if(o && m_capture(game::gamemode)) capture::returnaffinity(o, flag); 3569 break; 3570 } 3571 3572 case N_TAKEAFFIN: 3573 { 3574 int ocn = getint(p), flag = getint(p); 3575 gameent *o = game::newclient(ocn); 3576 if(o) 3577 { 3578 o->lastaffinity = lastmillis; 3579 if(m_capture(game::gamemode)) capture::takeaffinity(o, flag); 3580 else if(m_bomber(game::gamemode)) bomber::takeaffinity(o, flag); 3581 } 3582 break; 3583 } 3584 3585 case N_RESETAFFIN: 3586 { 3587 int flag = getint(p), value = getint(p); 3588 vec resetpos(-1, -1, -1); 3589 if(value == 2) loopk(3) resetpos[k] = getint(p)/DMF; 3590 if(m_capture(game::gamemode)) capture::resetaffinity(flag, value, resetpos); 3591 else if(m_bomber(game::gamemode)) bomber::resetaffinity(flag, value, resetpos); 3592 break; 3593 } 3594 3595 case N_GETMAP: 3596 { 3597 conoutf("\fyServer has requested we send the map.."); 3598 if(!needsmap && !gettingmap) sendmap(); 3599 else addmsg(N_GETMAP, "r"); 3600 break; 3601 } 3602 3603 case N_SENDMAP: 3604 { 3605 conoutf("\fyMap data has been uploaded to the server"); 3606 break; 3607 } 3608 3609 case N_FAILMAP: 3610 { 3611 conoutf("\fyFailed to get a valid map"); 3612 needsmap = gettingmap = 0; 3613 break; 3614 } 3615 3616 case N_NEWMAP: 3617 { 3618 int size = getint(p); 3619 getstring(text, p); 3620 if(size >= 0) emptymap(size, true, text); 3621 else enlargemap(size == -2, true); 3622 mapvotes.shrink(0); 3623 needsmap = 0; 3624 if(d) 3625 { 3626 int newsize = 0; 3627 while(1<<newsize < worldsize) newsize++; 3628 conoutf(size >= 0 ? "\fy%s started new map \fs\fc%s\fS of size \fs\fc%d\fS" : "\fy%s enlarged the map \fs\fc%s\fS to size \fs\fc%d\fS", game::colourname(d), mapname, newsize); 3629 } 3630 break; 3631 } 3632 3633 case N_INITAI: 3634 { 3635 int bn = getint(p), on = getint(p), at = getint(p), et = getint(p), sk = clamp(getint(p), 1, 101); 3636 getstring(text, p); 3637 int tm = getint(p), cl = getint(p), md = getint(p), pt = getint(p); 3638 string vanity; 3639 getstring(vanity, p); 3640 int lw = getint(p); 3641 vector<int> lweaps; 3642 loopk(lw) lweaps.add(getint(p)); 3643 gameent *b = game::newclient(bn); 3644 if(!b) break; 3645 ai::init(b, at, et, on, sk, bn, text, tm, cl, md, pt, vanity, lweaps); 3646 break; 3647 } 3648 3649 case N_AUTHCHAL: 3650 { 3651 uint id = (uint)getint(p); 3652 getstring(text, p); 3653 if(accountname[0] && accountpass[0]) 3654 { 3655 conoutf("Identifying as: \fs\fc%s\fS (\fs\fy%u\fS)", accountname, id); 3656 vector<char> buf; 3657 answerchallenge(accountpass, text, buf); 3658 addmsg(N_AUTHANS, "ris", id, buf.getbuf()); 3659 } 3660 break; 3661 } 3662 3663 case N_STEAMCHAL: 3664 { 3665 char token[1024]; 3666 uint tokenlen; 3667 if(cdpi::steam::clientauthticket(token, &tokenlen)) 3668 { 3669 packetbuf sc(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); 3670 putint(sc, N_STEAMANS); 3671 sendstring(cdpi::steam::steamuserid, sc); 3672 putuint(sc, tokenlen); 3673 if(tokenlen > 0) sc.put((uchar *)token, tokenlen); 3674 sendclientpacket(sc.finalize(), 1); 3675 conoutft(CON_EVENT, "\fyPerforming Steam ID challenge..."); 3676 } 3677 else 3678 { 3679 conoutft(CON_EVENT, "\frFailed to perform Steam ID challenge"); 3680 addmsg(N_STEAMFAIL, "r"); 3681 } 3682 break; 3683 } 3684 3685 case N_QUEUEPOS: 3686 { 3687 int qcn = getint(p), pos = getint(p); 3688 gameent *o = game::newclient(qcn); 3689 bool changed = o->queuepos != pos; 3690 o->queuepos = pos; 3691 if(o == game::focus && changed && o->state != CS_ALIVE) 3692 { 3693 if(o->queuepos < 0) conoutft(CON_EVENT, "\fyYou are now \fs\fzgcqueued\fS for the \fs\fcnext match\fS"); 3694 else if(o->queuepos) conoutft(CON_EVENT, "\fyYou are \fs\fzgc#%d\fS in the \fs\fcqueue\fS", o->queuepos+1); 3695 else conoutft(CON_EVENT, "\fyYou are \fs\fzgcNEXT\fS in the \fs\fcqueue\fS"); 3696 } 3697 break; 3698 } 3699 3700 default: 3701 { 3702 defformatstring(err, "type (got: %d, prev: %d, sender: %s)", type, prevtype, d ? game::colourname(d) : "<none>"); 3703 neterr(err); 3704 return; 3705 } 3706 } 3707 } 3708 } 3709 serverstat(serverinfo * a)3710 int serverstat(serverinfo *a) 3711 { 3712 if(a->attr.length() > 4 && a->numplayers >= a->attr[4]) 3713 { 3714 return SSTAT_FULL; 3715 } 3716 else if(a->attr.length() > 5) switch(a->attr[5]) 3717 { 3718 case MM_LOCKED: 3719 { 3720 return SSTAT_LOCKED; 3721 } 3722 case MM_PRIVATE: 3723 case MM_PASSWORD: 3724 { 3725 return SSTAT_PRIVATE; 3726 } 3727 default: 3728 { 3729 return SSTAT_OPEN; 3730 } 3731 } 3732 return SSTAT_UNKNOWN; 3733 } 3734 servercompare(serverinfo * a,serverinfo * b)3735 int servercompare(serverinfo *a, serverinfo *b) 3736 { 3737 int ac = 0, bc = 0; 3738 if(a->address.host == ENET_HOST_ANY || a->ping >= serverinfo::WAITING || a->attr.empty()) ac = -1; 3739 else ac = a->attr[0] == VERSION_GAME ? 0x7FFF : clamp(a->attr[0], 0, 0x7FFF-1); 3740 if(b->address.host == ENET_HOST_ANY || b->ping >= serverinfo::WAITING || b->attr.empty()) bc = -1; 3741 else bc = b->attr[0] == VERSION_GAME ? 0x7FFF : clamp(b->attr[0], 0, 0x7FFF-1); 3742 if(ac > bc) return -1; 3743 if(ac < bc) return 1; 3744 3745 #define retcp(c) { int cv = (c); if(cv) { return reverse ? -cv : cv; } } 3746 #define retsw(c,d,e) { \ 3747 int cv = (c), dv = (d); \ 3748 if(cv != dv) \ 3749 { \ 3750 if((e) != reverse ? cv < dv : cv > dv) return -1; \ 3751 if((e) != reverse ? cv > dv : cv < dv) return 1; \ 3752 } \ 3753 } 3754 3755 if(serversortstyles.empty()) updateserversort(); 3756 loopv(serversortstyles) 3757 { 3758 int style = serversortstyles[i]; 3759 serverinfo *aa = a, *ab = b; 3760 bool reverse = false; 3761 if(style < 0) 3762 { 3763 style = 0-style; 3764 reverse = true; 3765 } 3766 3767 switch(style) 3768 { 3769 case SINFO_STATUS: 3770 { 3771 retsw(serverstat(aa), serverstat(ab), true); 3772 break; 3773 } 3774 case SINFO_DESC: 3775 { 3776 retcp(strcmp(aa->sdesc, ab->sdesc)); 3777 break; 3778 } 3779 case SINFO_MODE: 3780 { 3781 if(aa->attr.length() > 1) ac = aa->attr[1]; 3782 else ac = 0; 3783 3784 if(ab->attr.length() > 1) bc = ab->attr[1]; 3785 else bc = 0; 3786 3787 retsw(ac, bc, true); 3788 } 3789 case SINFO_MUTS: 3790 { 3791 if(aa->attr.length() > 2) ac = aa->attr[2]; 3792 else ac = 0; 3793 3794 if(ab->attr.length() > 2) bc = ab->attr[2]; 3795 else bc = 0; 3796 3797 retsw(ac, bc, true); 3798 break; 3799 } 3800 case SINFO_MAP: 3801 { 3802 retcp(strcmp(aa->map, ab->map)); 3803 break; 3804 } 3805 case SINFO_TIME: 3806 { 3807 if(aa->attr.length() > 3) ac = aa->attr[3]; 3808 else ac = 0; 3809 3810 if(ab->attr.length() > 3) bc = ab->attr[3]; 3811 else bc = 0; 3812 3813 retsw(ac, bc, false); 3814 break; 3815 } 3816 case SINFO_NUMPLRS: 3817 { 3818 retsw(aa->numplayers, ab->numplayers, false); 3819 break; 3820 } 3821 case SINFO_MAXPLRS: 3822 { 3823 if(aa->attr.length() > 4) ac = aa->attr[4]; 3824 else ac = 0; 3825 3826 if(ab->attr.length() > 4) bc = ab->attr[4]; 3827 else bc = 0; 3828 3829 retsw(ac, bc, false); 3830 break; 3831 } 3832 case SINFO_PING: 3833 { 3834 retsw(aa->ping, ab->ping, true); 3835 break; 3836 } 3837 case SINFO_PRIO: 3838 { 3839 retsw(aa->priority, ab->priority, false); 3840 break; 3841 } 3842 default: break; 3843 } 3844 } 3845 return strcmp(a->name, b->name); 3846 } 3847 parsepacketclient(int chan,packetbuf & p)3848 void parsepacketclient(int chan, packetbuf &p) // processes any updates from the server 3849 { 3850 if(p.packet->flags&ENET_PACKET_FLAG_UNSEQUENCED) return; 3851 switch(chan) 3852 { 3853 case 0: 3854 parsepositions(p); 3855 break; 3856 3857 case 1: 3858 parsemessages(-1, NULL, p); 3859 break; 3860 3861 case 2: 3862 receivefile(p.buf, p.maxlen); 3863 break; 3864 } 3865 } 3866 getservers(int server,int prop,int idx)3867 void getservers(int server, int prop, int idx) 3868 { 3869 if(server < 0) intret(servers.length()); 3870 else if(servers.inrange(server)) 3871 { 3872 serverinfo *si = servers[server]; 3873 if(prop < 0) intret(4); 3874 else switch(prop) 3875 { 3876 case 0: 3877 if(idx < 0) intret(11); 3878 else switch(idx) 3879 { 3880 case 0: intret(serverstat(si)); break; 3881 case 1: result(si->name); break; 3882 case 2: intret(si->port); break; 3883 case 3: result(si->sdesc[0] ? si->sdesc : si->name); break; 3884 case 4: result(si->map); break; 3885 case 5: intret(si->numplayers); break; 3886 case 6: intret(si->ping); break; 3887 case 7: intret(si->lastinfo); break; 3888 case 8: result(si->authhandle); break; 3889 case 9: result(si->flags); break; 3890 case 10: result(si->branch); break; 3891 case 11: intret(si->priority); break; 3892 } 3893 break; 3894 case 1: 3895 if(idx < 0) intret(si->attr.length()); 3896 else if(si->attr.inrange(idx)) intret(si->attr[idx]); 3897 break; 3898 case 2: 3899 if(idx < 0) intret(si->players.length()); 3900 else if(si->players.inrange(idx)) result(si->players[idx]); 3901 break; 3902 case 3: 3903 if(idx < 0) intret(si->handles.length()); 3904 else if(si->handles.inrange(idx)) result(si->handles[idx]); 3905 break; 3906 } 3907 } 3908 } 3909 ICOMMAND(0, getserver, "bbb", (int *server, int *prop, int *idx), getservers(*server, *prop, *idx)); 3910 3911 #define GETSERVER(name, test, incr) \ 3912 ICOMMAND(0, getserver##name, "", (), \ 3913 { \ 3914 int n = 0; \ 3915 loopv(servers) if(test) n += incr; \ 3916 intret(n); \ 3917 }); \ 3918 ICOMMAND(0, getserver##name##if, "re", (ident *id, uint *cond), \ 3919 { \ 3920 int n = 0; \ 3921 loopstart(id, stack); \ 3922 loopv(servers) \ 3923 { \ 3924 loopiter(id, stack, i); \ 3925 if(test && executebool(cond)) n += incr; \ 3926 } \ 3927 loopend(id, stack); \ 3928 intret(n); \ 3929 }); 3930 3931 GETSERVER(count, true, 1); 3932 GETSERVER(active, servers[i]->numplayers, 1); 3933 GETSERVER(players, servers[i]->numplayers, servers[i]->numplayers); 3934 loopserver(ident * id,uint * cond,uint * body,uint * none)3935 void loopserver(ident *id, uint *cond, uint *body, uint *none) 3936 { 3937 loopstart(id, stack); 3938 if(servers.empty()) 3939 { 3940 loopiter(id, stack, -1); 3941 execute(none); 3942 } 3943 else loopv(servers) 3944 { 3945 loopiter(id, stack, i); 3946 if(cond && !executebool(cond)) continue; 3947 execute(body); 3948 } 3949 loopend(id, stack); 3950 } 3951 3952 ICOMMAND(0, loopservers, "ree", (ident *id, uint *body, uint *none), loopserver(id, NULL, body, none)); 3953 ICOMMAND(0, loopserversif, "reee", (ident *id, uint *cond, uint *body, uint *none), loopserver(id, cond, body, none)); 3954 completeplayers(const char ** nextcomplete,const char * start,int commandsize,const char * lastcomplete,bool reverse)3955 void completeplayers(const char **nextcomplete, const char *start, int commandsize, const char *lastcomplete, bool reverse) 3956 { 3957 static vector<char *> names; 3958 if(!names.empty()) names.deletearrays(); 3959 gameent *d = NULL; 3960 int size = completesize-commandsize; 3961 const char *name = game::colourname(game::player1, NULL, false, true, 0); 3962 if(!size || !strncmp(name, &start[commandsize], size)) 3963 names.add(newstring(name)); 3964 loopv(game::players) if((d = game::players[i]) != NULL) 3965 { 3966 name = game::colourname(d, NULL, false, true, 0); 3967 if(!size || !strncmp(name, &start[commandsize], size)) 3968 names.add(newstring(name)); 3969 } 3970 loopv(names) 3971 { 3972 if(strcmp(names[i], lastcomplete)*(reverse ? -1 : 1) > 0 && (!*nextcomplete || strcmp(names[i], *nextcomplete)*(reverse ? -1 : 1) < 0)) 3973 *nextcomplete = names[i]; 3974 } 3975 } 3976 } 3977