1 #include "game.h" 2 namespace client 3 { 4 bool sendinfo = false, isready = false, remote = false, 5 demoplayback = false, needsmap = false, gettingmap = false, sendcrc = false; 6 int lastping = 0, sessionid = 0; 7 string connectpass = ""; 8 vector<mapvote> mapvotes; 9 vote(gameent * d,const char * text,int mode,int muts)10 void vote(gameent *d, const char *text, int mode, int muts) 11 { 12 mapvote *m = NULL; 13 loopv(mapvotes) if(mapvotes[i].player == d) { m = &mapvotes[i]; break; } 14 if(!m) m = &mapvotes.add(); 15 m->player = d; 16 copystring(m->map, text); 17 m->mode = mode; 18 m->muts = muts; 19 SEARCHBINDCACHE(votekey)("showgui vote", 0); 20 SEARCHBINDCACHE(gamekey)("showgui game", 0); 21 conoutft(CON_MESG, "\fc%s suggests: \fs\fw%s on %s, press \fs\fc%s\fS to vote or \fs\fc%s\fS to select your own", game::colorname(d), server::gamename(mode, muts), text, votekey, gamekey); 22 } getvotes(int vote)23 void getvotes(int vote) 24 { 25 if(!vote) intret(mapvotes.length()); 26 else 27 { 28 mkstring(text); 29 if(mapvotes.inrange(--vote)) formatstring(text)("%d %d %d %s", mapvotes[vote].player->clientnum, mapvotes[vote].mode, mapvotes[vote].muts, mapvotes[vote].map); 30 result(text); 31 } 32 } 33 ICOMMAND(getvote, "i", (int *num), getvotes(*num)); 34 35 int lastauth = 0; 36 string authname = "", authkey = ""; 37 setauthkey(const char * name,const char * key)38 void setauthkey(const char *name, const char *key) 39 { 40 copystring(authname, name); 41 copystring(authkey, key); 42 } 43 ICOMMAND(authkey, "ss", (char *name, char *key), setauthkey(name, key)); 44 45 // collect c2s messages conveniently 46 vector<uchar> messages; 47 bool messagereliable = false; 48 49 VARP(colourchat, 0, 1, 1); 50 VARP(showlaptimes, 0, 2, 3); // 0 = off, 1 = only player, 2 = +humans, 3 = +bots 51 SVARP(serversort, ""); 52 53 ICOMMAND(mastermode, "i", (int *val), addmsg(SV_MASTERMODE, "ri", *val)); 54 ICOMMAND(getname, "", (), result(game::player1->name)); 55 ICOMMAND(getteam, "", (), result(teamtype[game::player1->team].name)); 56 ICOMMAND(getteamicon, "", (), result(hud::teamtex(game::player1->team))); 57 getname()58 const char *getname() { return game::player1->name; } 59 switchname(const char * name)60 void switchname(const char *name) 61 { 62 if(name[0]) 63 { 64 string text; filtertext(text, name); 65 copystring(game::player1->name, text, MAXNAMELEN); 66 addmsg(SV_SWITCHNAME, "rs", game::player1->name); 67 } 68 else conoutft(CON_MESG, "\fgyour name is: %s", *game::player1->name ? game::colorname(game::player1) : "<not set>"); 69 } 70 ICOMMAND(name, "s", (char *s), switchname(s)); 71 teamname(const char * team)72 int teamname(const char *team) 73 { 74 if(m_fight(game::gamemode) && m_team(game::gamemode, game::mutators)) 75 { 76 if(team[0]) 77 { 78 int t = atoi(team); 79 80 loopi(numteams(game::gamemode, game::mutators)) 81 { 82 if((t && t == i+TEAM_FIRST) || !strcasecmp(teamtype[i+TEAM_FIRST].name, team)) 83 { 84 return i+TEAM_FIRST; 85 } 86 } 87 } 88 return TEAM_FIRST; 89 } 90 return TEAM_NEUTRAL; 91 } 92 switchteam(const char * team)93 void switchteam(const char *team) 94 { 95 if(team[0]) 96 { 97 if(m_fight(game::gamemode) && m_team(game::gamemode, game::mutators) && game::player1->state != CS_SPECTATOR && game::player1->state != CS_EDITING) 98 { 99 int t = teamname(team); 100 if(t != game::player1->team) 101 { 102 if(game::player1->team != t) hud::lastteam = 0; 103 game::player1->team = t; 104 addmsg(SV_SWITCHTEAM, "ri", game::player1->team); 105 } 106 } 107 else conoutft(CON_MESG, "\frcan only change teams when actually playing in team games"); 108 } 109 else conoutft(CON_MESG, "\fs\fgyour team is:\fS \fs%s%s\fS", teamtype[game::player1->team].chat, teamtype[game::player1->team].name); 110 } 111 ICOMMAND(team, "s", (char *s), switchteam(s)); 112 numchannels()113 int numchannels() { return 3; } 114 writeclientinfo(stream * f)115 void writeclientinfo(stream *f) 116 { 117 f->printf("name \"%s\"\n\n", game::player1->name); 118 } 119 connectattempt(const char * name,int port,int qport,const char * password,const ENetAddress & address)120 void connectattempt(const char *name, int port, int qport, const char *password, const ENetAddress &address) 121 { 122 copystring(connectpass, password); 123 } 124 connectfail()125 void connectfail() 126 { 127 memset(connectpass, 0, sizeof(connectpass)); 128 } 129 gameconnect(bool _remote)130 void gameconnect(bool _remote) 131 { 132 remote = _remote; 133 if(editmode) toggleedit(); 134 } 135 gamedisconnect(int clean)136 void gamedisconnect(int clean) 137 { 138 if(editmode) toggleedit(); 139 gettingmap = needsmap = remote = isready = sendinfo = false; 140 sessionid = 0; 141 messages.setsize(0); 142 mapvotes.setsize(0); 143 messagereliable = false; 144 projs::remove(game::player1); 145 removetrackedparticles(game::player1); 146 removetrackedsounds(game::player1); 147 game::player1->clientnum = -1; 148 game::player1->privilege = PRIV_NONE; 149 game::gamemode = game::mutators = -1; 150 loopv(game::players) if(game::players[i]) game::clientdisconnected(i); 151 emptymap(0, true, NULL, true); 152 smartmusic(true, false); 153 enumerate(*idents, ident, id, { 154 if(id.flags&IDF_CLIENT) // reset vars 155 { 156 switch(id.type) 157 { 158 case ID_VAR: 159 { 160 setvar(id.name, id.def.i, true); 161 break; 162 } 163 case ID_FVAR: 164 { 165 setfvar(id.name, id.def.f, true); 166 break; 167 } 168 case ID_SVAR: 169 { 170 setsvar(id.name, *id.def.s ? id.def.s : "", true); 171 break; 172 } 173 default: break; 174 } 175 } 176 }); 177 if(clean) 178 { 179 game::clientmap[0] = '\0'; 180 } 181 } 182 allowedittoggle(bool edit)183 bool allowedittoggle(bool edit) 184 { 185 bool allow = edit || m_edit(game::gamemode); // && game::player1->state == CS_ALIVE); 186 if(!allow) conoutft(CON_MESG, "\fryou must be in edit mode to start editing"); 187 return allow; 188 } 189 edittoggled(bool edit)190 void edittoggled(bool edit) 191 { 192 game::player1->editspawn(lastmillis, m_weapon(game::gamemode, game::mutators), m_health(game::gamemode, game::mutators), m_arena(game::gamemode, game::mutators), spawngrenades >= (m_insta(game::gamemode, game::mutators) ? 2 : 1)); 193 game::player1->state = edit ? CS_EDITING : CS_ALIVE; 194 game::player1->resetinterp(); 195 game::resetstate(); 196 physics::entinmap(game::player1, true); // find spawn closest to current floating pos 197 projs::remove(game::player1); 198 if(m_edit(game::gamemode)) addmsg(SV_EDITMODE, "ri", edit ? 1 : 0); 199 entities::edittoggled(edit); 200 } 201 getclientname(int cn)202 const char *getclientname(int cn) 203 { 204 gameent *d = game::getclient(cn); 205 return d ? d->name : ""; 206 } 207 ICOMMAND(getclientname, "i", (int *cn), result(getclientname(*cn))); 208 getclientteam(int cn)209 int getclientteam(int cn) 210 { 211 gameent *d = game::getclient(cn); 212 return d ? d->team : -1; 213 } 214 ICOMMAND(getclientteam, "i", (int *cn), intret(getclientteam(*cn))); 215 isspectator(int cn)216 bool isspectator(int cn) 217 { 218 gameent *d = game::getclient(cn); 219 return d->state==CS_SPECTATOR; 220 } 221 ICOMMAND(isspectator, "i", (int *cn), intret(isspectator(*cn) ? 1 : 0)); 222 isai(int cn,int type)223 bool isai(int cn, int type) 224 { 225 gameent *d = game::getclient(cn); 226 int aitype = type > 0 && type < AI_MAX ? type : AI_BOT; 227 return d->aitype==aitype; 228 } 229 ICOMMAND(isai, "ii", (int *cn, int *type), intret(isai(*cn, *type) ? 1 : 0)); 230 parseplayer(const char * arg)231 int parseplayer(const char *arg) 232 { 233 char *end; 234 int n = strtol(arg, &end, 10); 235 if(*arg && !*end) 236 { 237 if(n!=game::player1->clientnum && !game::players.inrange(n)) return -1; 238 return n; 239 } 240 // try case sensitive first 241 loopv(game::players) if(game::players[i]) 242 { 243 gameent *o = game::players[i]; 244 if(!strcmp(arg, o->name)) return o->clientnum; 245 } 246 // nothing found, try case insensitive 247 loopv(game::players) if(game::players[i]) 248 { 249 gameent *o = game::players[i]; 250 if(!strcasecmp(arg, o->name)) return o->clientnum; 251 } 252 return -1; 253 } 254 ICOMMAND(getclientnum, "s", (char *name), intret(name[0] ? parseplayer(name) : game::player1->clientnum)); 255 listclients(bool local)256 void listclients(bool local) 257 { 258 vector<char> buf; 259 string cn; 260 int numclients = 0; 261 if(local) 262 { 263 formatstring(cn)("%d", game::player1->clientnum); 264 buf.put(cn, strlen(cn)); 265 numclients++; 266 } 267 loopv(game::players) if(game::players[i]) 268 { 269 formatstring(cn)("%d", game::players[i]->clientnum); 270 if(numclients++) buf.add(' '); 271 buf.put(cn, strlen(cn)); 272 } 273 buf.add('\0'); 274 result(buf.getbuf()); 275 } 276 ICOMMAND(listclients, "i", (int *local), listclients(*local!=0)); 277 clearbans()278 void clearbans() 279 { 280 addmsg(SV_CLEARBANS, "r"); 281 } 282 ICOMMAND(clearbans, "", (char *s), clearbans()); 283 kick(const char * arg)284 void kick(const char *arg) 285 { 286 int i = parseplayer(arg); 287 if(i>=0 && i!=game::player1->clientnum) addmsg(SV_KICK, "ri", i); 288 } 289 ICOMMAND(kick, "s", (char *s), kick(s)); 290 setteam(const char * arg1,const char * arg2)291 void setteam(const char *arg1, const char *arg2) 292 { 293 if(m_fight(game::gamemode) && m_team(game::gamemode, game::mutators)) 294 { 295 int i = parseplayer(arg1); 296 if(i>=0 && i!=game::player1->clientnum) 297 { 298 int t = teamname(arg2); 299 if(t) addmsg(SV_SETTEAM, "ri2", i, t); 300 } 301 } 302 else conoutft(CON_MESG, "\frcan only change teams in team games"); 303 } 304 ICOMMAND(setteam, "ss", (char *who, char *team), setteam(who, team)); 305 hashpwd(const char * pwd)306 void hashpwd(const char *pwd) 307 { 308 if(game::player1->clientnum<0) return; 309 string hash; 310 server::hashpassword(game::player1->clientnum, sessionid, pwd, hash); 311 result(hash); 312 } 313 COMMAND(hashpwd, "s"); 314 setmaster(const char * arg)315 void setmaster(const char *arg) 316 { 317 if(!arg[0]) return; 318 int val = 1; 319 mkstring(hash); 320 if(!arg[1] && isdigit(arg[0])) val = atoi(arg); 321 else server::hashpassword(game::player1->clientnum, sessionid, arg, hash); 322 addmsg(SV_SETMASTER, "ris", val, hash); 323 } 324 COMMAND(setmaster, "s"); 325 tryauth()326 void tryauth() 327 { 328 if(!authname[0]) return; 329 lastauth = lastmillis; 330 addmsg(SV_AUTHTRY, "rs", authname); 331 } 332 ICOMMAND(auth, "", (), tryauth()); 333 togglespectator(int val,const char * who)334 void togglespectator(int val, const char *who) 335 { 336 int i = who[0] ? parseplayer(who) : game::player1->clientnum; 337 if(i>=0) addmsg(SV_SPECTATOR, "rii", i, val); 338 } 339 ICOMMAND(spectator, "is", (int *val, char *who), togglespectator(*val, who)); 340 addmsg(int type,const char * fmt,...)341 void addmsg(int type, const char *fmt, ...) 342 { 343 static uchar buf[MAXTRANS]; 344 ucharbuf p(buf, MAXTRANS); 345 putint(p, type); 346 int numi = 1, nums = 0; 347 bool reliable = false; 348 if(fmt) 349 { 350 va_list args; 351 va_start(args, fmt); 352 while(*fmt) switch(*fmt++) 353 { 354 case 'r': reliable = true; break; 355 case 'v': 356 { 357 int n = va_arg(args, int); 358 int *v = va_arg(args, int *); 359 loopi(n) putint(p, v[i]); 360 numi += n; 361 break; 362 } 363 case 'i': 364 { 365 int n = isdigit(*fmt) ? *fmt++-'0' : 1; 366 loopi(n) putint(p, va_arg(args, int)); 367 numi += n; 368 break; 369 } 370 case 'f': 371 { 372 int n = isdigit(*fmt) ? *fmt++-'0' : 1; 373 loopi(n) putfloat(p, (float)va_arg(args, double)); 374 numi += n; 375 break; 376 } 377 case 's': sendstring(va_arg(args, const char *), p); nums++; break; 378 } 379 va_end(args); 380 } 381 int num = nums?0:numi, msgsize = msgsizelookup(type); 382 if(msgsize && num!=msgsize) { defformatstring(s)("inconsistent msg size for %d (%d != %d)", type, num, msgsize); fatal(s); } 383 if(reliable) messagereliable = true; 384 messages.put(buf, p.length()); 385 } 386 saytext(gameent * d,int flags,char * text)387 void saytext(gameent *d, int flags, char *text) 388 { 389 if(!colourchat) filtertext(text, text); 390 mkstring(s); 391 bool team = flags&SAY_TEAM; 392 defformatstring(m)("%s", game::colorname(d)); 393 if(team) 394 { 395 defformatstring(t)(" (\fs%s%s\fS)", teamtype[d->team].chat, teamtype[d->team].name); 396 concatstring(m, t); 397 } 398 if(flags&SAY_ACTION) formatstring(s)("\fm* \fs%s\fS \fs\fm%s\fS", m, text); 399 else formatstring(s)("\fa<\fs\fw%s\fS> \fs\fw%s\fS", m, text); 400 401 if(d->state != CS_SPECTATOR) 402 { 403 defformatstring(ds)("<sub>%s", s); 404 part_textcopy(d->abovehead(), ds, PART_TEXT, game::aboveheadfade, 0xFFFFFF, 2, 1, -10, 0, d); 405 } 406 407 conoutft(CON_CHAT, "%s", s); 408 playsound(S_CHAT, camera1->o, camera1, SND_FORCED); 409 } 410 toserver(int flags,char * text)411 void toserver(int flags, char *text) 412 { 413 if(ready()) 414 { 415 saytext(game::player1, flags, text); 416 addmsg(SV_TEXT, "ri2s", game::player1->clientnum, flags, text); 417 } 418 } 419 ICOMMAND(say, "C", (char *s), toserver(SAY_NONE, s)); 420 ICOMMAND(me, "C", (char *s), toserver(SAY_ACTION, s)); 421 ICOMMAND(sayteam, "C", (char *s), toserver(SAY_TEAM, s)); 422 ICOMMAND(meteam, "C", (char *s), toserver(SAY_ACTION|SAY_TEAM, s)); 423 parsecommand(gameent * d,const char * cmd,const char * arg)424 void parsecommand(gameent *d, const char *cmd, const char *arg) 425 { 426 ident *id = idents->access(cmd); 427 if(id && id->flags&IDF_CLIENT) 428 { 429 string val; 430 val[0] = 0; 431 switch(id->type) 432 { 433 #if 0 // these shouldn't get here 434 case ID_COMMAND: 435 case ID_COMMAND: 436 { 437 string s; 438 formatstring(s)("%s %s", cmd, arg); 439 char *ret = executeret(s); 440 if(ret) 441 { 442 conoutft(CON_MESG, "\fg%s: %s", cmd, ret); 443 delete[] ret; 444 } 445 return; 446 } 447 #endif 448 case ID_VAR: 449 { 450 int ret = atoi(arg); 451 *id->storage.i = ret; 452 id->changed(); 453 formatstring(val)(id->flags&IDF_HEX ? (id->maxval==0xFFFFFF ? "0x%.6X" : "0x%X") : "%d", *id->storage.i); 454 break; 455 } 456 case ID_FVAR: 457 { 458 float ret = atof(arg); 459 *id->storage.f = ret; 460 id->changed(); 461 formatstring(val)("%s", floatstr(*id->storage.f)); 462 break; 463 } 464 case ID_SVAR: 465 { 466 delete[] *id->storage.s; 467 *id->storage.s = newstring(arg); 468 id->changed(); 469 formatstring(val)("%s", *id->storage.s); 470 break; 471 } 472 default: return; 473 } 474 if(d || verbose >= 2) 475 conoutft(d != game::player1 ? CON_INFO : CON_MESG, "\fg%s set %s to %s", d ? game::colorname(d) : "the server", cmd, val); 476 } 477 else conoutft(d != game::player1 ? CON_INFO : CON_MESG, "\fr%s sent unknown command: %s", d ? game::colorname(d) : "the server", cmd); 478 } 479 sendcmd(int nargs,const char * cmd,const char * arg)480 bool sendcmd(int nargs, const char *cmd, const char *arg) 481 { 482 if(ready()) 483 { 484 addmsg(SV_COMMAND, "ri2ss", game::player1->clientnum, nargs, cmd, arg); 485 return true; 486 } 487 else 488 { 489 defformatstring(scmd)("sv_%s", cmd); 490 if(server::servcmd(nargs, scmd, arg)) 491 { 492 parsecommand(NULL, cmd, arg); 493 return true; 494 } 495 } 496 return false; 497 } 498 changemapserv(char * name,int gamemode,int mutators,bool temp)499 void changemapserv(char *name, int gamemode, int mutators, bool temp) 500 { 501 if(editmode) toggleedit(); 502 game::gamemode = gamemode; game::mutators = mutators; 503 server::modecheck(&game::gamemode, &game::mutators); 504 game::nextmode = game::gamemode; game::nextmuts = game::mutators; 505 game::minremain = -1; 506 game::maptime = 0; 507 mapvotes.setsize(0); 508 if(editmode && !allowedittoggle(editmode)) toggleedit(); 509 if(m_demo(gamemode)) return; 510 needsmap = false; 511 if(!name || !*name || !load_world(name, temp)) 512 { 513 emptymap(0, true, NULL); 514 setnames(name, MAP_BFGZ); 515 needsmap = true; 516 } 517 if(editmode) edittoggled(editmode); 518 if(m_stf(gamemode)) stf::setupflags(); 519 else if(m_ctf(gamemode)) ctf::setupflags(); 520 } 521 receivefile(uchar * data,int len)522 void receivefile(uchar *data, int len) 523 { 524 ucharbuf p(data, len); 525 int type = getint(p); 526 data += p.length(); 527 len -= p.length(); 528 switch(type) 529 { 530 case SV_SENDDEMO: 531 { 532 defformatstring(fname)("%d.dmo", lastmillis); 533 stream *demo = openfile(fname, "wb"); 534 if(!demo) return; 535 conoutft(CON_MESG, "received demo \"%s\"", fname); 536 demo->write(data, len); 537 delete demo; 538 break; 539 } 540 541 case SV_SENDMAPCONFIG: 542 case SV_SENDMAPSHOT: 543 case SV_SENDMAPFILE: 544 { 545 const char *reqmap = mapname, *reqext = "xxx"; 546 if(type == SV_SENDMAPCONFIG) reqext = "cfg"; 547 else if(type == SV_SENDMAPSHOT) reqext = "png"; 548 else if(type == SV_SENDMAPFILE) reqext = "bgz"; 549 if(!reqmap || !*reqmap) reqmap = "maps/untitled"; 550 defformatstring(reqfile)(strstr(reqmap, "temp/")==reqmap || strstr(reqmap, "temp\\")==reqmap ? "%s" : "temp/%s", reqmap); 551 defformatstring(reqfext)("%s.%s", reqfile, reqext); 552 stream *f = openfile(reqfext, "wb"); 553 if(!f) 554 { 555 conoutf("\frfailed to open map file: %s", reqfext); 556 return; 557 } 558 gettingmap = true; 559 f->write(data, len); 560 delete f; 561 break; 562 } 563 } 564 return; 565 } 566 ICOMMAND(getmap, "", (), addmsg(SV_GETMAP, "r")); 567 stopdemo()568 void stopdemo() 569 { 570 if(remote) addmsg(SV_STOPDEMO, "r"); 571 else server::stopdemo(); 572 } 573 ICOMMAND(stopdemo, "", (), stopdemo()); 574 recorddemo(int val)575 void recorddemo(int val) 576 { 577 addmsg(SV_RECORDDEMO, "ri", val); 578 } 579 ICOMMAND(recorddemo, "i", (int *val), recorddemo(*val)); 580 cleardemos(int val)581 void cleardemos(int val) 582 { 583 addmsg(SV_CLEARDEMOS, "ri", val); 584 } 585 ICOMMAND(cleardemos, "i", (int *val), cleardemos(*val)); 586 getdemo(int i)587 void getdemo(int i) 588 { 589 if(i<=0) conoutf("getting demo..."); 590 else conoutf("getting demo %d...", i); 591 addmsg(SV_GETDEMO, "ri", i); 592 } 593 ICOMMAND(getdemo, "i", (int *val), getdemo(*val)); 594 listdemos()595 void listdemos() 596 { 597 conoutf("listing demos..."); 598 addmsg(SV_LISTDEMOS, "r"); 599 } 600 ICOMMAND(listdemos, "", (), listdemos()); 601 changemap(const char * name)602 void changemap(const char *name) // request map change, server may ignore 603 { 604 int nextmode = game::nextmode, nextmuts = game::nextmuts; // in case stopdemo clobbers these 605 if(!remote) stopdemo(); 606 if(!connected()) 607 { 608 server::changemap(name, nextmode, nextmuts); 609 localconnect(true); 610 } 611 else 612 { 613 string reqfile; 614 copystring(reqfile, !strncasecmp(name, "temp/", 5) || !strncasecmp(name, "temp\\", 5) ? name+5 : name); 615 addmsg(SV_MAPVOTE, "rsi2", reqfile, nextmode, nextmuts); 616 } 617 } 618 ICOMMAND(map, "s", (char *s), changemap(s)); 619 sendmap()620 void sendmap() 621 { 622 conoutf("sending map..."); 623 const char *reqmap = mapname; 624 if(!reqmap || !*reqmap) reqmap = "maps/untitled"; 625 bool edit = m_edit(game::gamemode); 626 defformatstring(reqfile)("%s%s", edit ? "temp/" : "", reqmap); 627 loopi(3) 628 { 629 string reqfext; 630 switch(i) 631 { 632 case 2: formatstring(reqfext)("%s.cfg", reqfile); break; 633 case 1: formatstring(reqfext)("%s.png", reqfile); break; 634 case 0: default: 635 formatstring(reqfext)("%s.bgz", reqfile); 636 if(edit) 637 { 638 save_world(reqfile, edit, true); 639 setnames(reqmap, MAP_BFGZ); 640 } 641 break; 642 } 643 stream *f = openfile(reqfext, "rb"); 644 if(f) 645 { 646 conoutf("\fgtransmitting file: %s", reqfext); 647 sendfile(-1, 2, f, "ri", SV_SENDMAPFILE+i); 648 delete f; 649 } 650 else conoutf("\frfailed to open map file: %s", reqfext); 651 } 652 } 653 ICOMMAND(sendmap, "", (), sendmap()); 654 gotoplayer(const char * arg)655 void gotoplayer(const char *arg) 656 { 657 if(game::player1->state!=CS_SPECTATOR && game::player1->state!=CS_EDITING) return; 658 int i = parseplayer(arg); 659 if(i>=0 && i!=game::player1->clientnum) 660 { 661 gameent *d = game::getclient(i); 662 if(!d) return; 663 game::player1->o = d->o; 664 vec dir; 665 vecfromyawpitch(game::player1->yaw, game::player1->pitch, 1, 0, dir); 666 game::player1->o.add(dir.mul(-32)); 667 game::player1->resetinterp(); 668 } 669 } 670 ICOMMAND(goto, "s", (char *s), gotoplayer(s)); 671 ready()672 bool ready() { return connected(false) && isready && game::maptime > 0; } 673 ICOMMAND(ready, "", (), intret(ready())); 674 state()675 int state() { return game::player1->state; } otherclients()676 int otherclients() 677 { 678 int n = 0; // ai don't count 679 loopv(game::players) if(game::players[i] && game::players[i]->aitype < 0) n++; 680 return n; 681 } 682 editvar(ident * id,bool local)683 void editvar(ident *id, bool local) 684 { 685 if(id && id->flags&IDF_WORLD && local && m_edit(game::gamemode)) 686 { 687 switch(id->type) 688 { 689 case ID_VAR: 690 addmsg(SV_EDITVAR, "risi", id->type, id->name, *id->storage.i); 691 break; 692 case ID_FVAR: 693 addmsg(SV_EDITVAR, "risf", id->type, id->name, *id->storage.f); 694 break; 695 case ID_SVAR: 696 addmsg(SV_EDITVAR, "riss", id->type, id->name, *id->storage.s); 697 break; 698 case ID_ALIAS: 699 addmsg(SV_EDITVAR, "riss", id->type, id->name, id->action); 700 break; 701 default: break; 702 } 703 } 704 } 705 edittrigger(const selinfo & sel,int op,int arg1,int arg2,int arg3)706 void edittrigger(const selinfo &sel, int op, int arg1, int arg2, int arg3) 707 { 708 if(m_edit(game::gamemode)) switch(op) 709 { 710 case EDIT_FLIP: 711 case EDIT_COPY: 712 case EDIT_PASTE: 713 case EDIT_DELCUBE: 714 { 715 addmsg(SV_EDITF + op, "ri9i4", 716 sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, 717 sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner); 718 break; 719 } 720 case EDIT_MAT: 721 case EDIT_ROTATE: 722 { 723 addmsg(SV_EDITF + op, "ri9i5", 724 sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, 725 sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner, 726 arg1); 727 break; 728 } 729 case EDIT_FACE: 730 case EDIT_TEX: 731 case EDIT_REPLACE: 732 { 733 addmsg(SV_EDITF + op, "ri9i6", 734 sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, 735 sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner, 736 arg1, arg2); 737 break; 738 } 739 case EDIT_REMIP: 740 { 741 addmsg(SV_EDITF + op, "r"); 742 break; 743 } 744 } 745 } 746 sendintro()747 void sendintro() 748 { 749 packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); 750 putint(p, SV_CONNECT); 751 sendstring(game::player1->name, p); 752 mkstring(hash); 753 if(connectpass[0]) 754 { 755 server::hashpassword(game::player1->clientnum, sessionid, connectpass, hash); 756 memset(connectpass, 0, sizeof(connectpass)); 757 } 758 sendstring(hash, p); 759 sendclientpacket(p.finalize(), 1); 760 } 761 updateposition(gameent * d)762 void updateposition(gameent *d) 763 { 764 if((d->state==CS_ALIVE || d->state==CS_EDITING) && (!d->ai || !d->ai->suspended)) 765 { 766 // send position updates separately so as to not stall out aiming 767 packetbuf q(100); 768 putint(q, SV_POS); 769 putint(q, d->clientnum); 770 putuint(q, (int)(d->o.x*DMF)); // quantize coordinates to 1/4th of a cube, between 1 and 3 bytes 771 putuint(q, (int)(d->o.y*DMF)); 772 putuint(q, (int)((d->o.z - d->height)*DMF)); 773 putuint(q, (int)d->yaw); 774 putint(q, (int)d->pitch); 775 putint(q, (int)d->roll); 776 putint(q, (int)(d->vel.x*DVELF)); // quantize to itself, almost always 1 byte 777 putint(q, (int)(d->vel.y*DVELF)); 778 putint(q, (int)(d->vel.z*DVELF)); 779 putuint(q, d->physstate | (d->falling.x || d->falling.y ? 0x20 : 0) | (d->falling.z ? 0x10 : 0)); 780 if(d->falling.x || d->falling.y) 781 { 782 putint(q, (int)(d->falling.x*DVELF)); // quantize to itself, almost always 1 byte 783 putint(q, (int)(d->falling.y*DVELF)); 784 } 785 if(d->falling.z) putint(q, (int)(d->falling.z*DVELF)); 786 // pack rest in almost always 1 byte: strafe:2, move:2, crouching: 1, aimyaw/aimpitch: 1 787 uint flags = (d->strafe&3) | ((d->move&3)<<2) | 788 ((d->action[AC_CROUCH] ? 1 : 0)<<4) | ((FWV(impulsestyle) && (d->turnside || ((d->action[AC_IMPULSE] && (d->move || d->strafe)))) ? 1 : 0)<<6) | ((d->conopen ? 1 : 0)<<7) | 789 ((int)d->aimyaw!=(int)d->yaw || (int)d->aimpitch!=(int)d->pitch ? 0x20 : 0); 790 putuint(q, flags); 791 if(flags&0x20) 792 { 793 putuint(q, (int)d->aimyaw); 794 putint(q, (int)d->aimpitch); 795 } 796 sendclientpacket(q.finalize(), 0); 797 } 798 } 799 sendmessages(gameent * d)800 void sendmessages(gameent *d) 801 { 802 packetbuf p(MAXTRANS); 803 if(sendcrc) 804 { 805 p.reliable(); 806 sendcrc = false; 807 putint(p, SV_MAPCRC); 808 sendstring(game::clientmap, p); 809 putint(p, game::clientmap[0] ? getmapcrc() : 0); 810 } 811 if(sendinfo && !needsmap) 812 { 813 p.reliable(); 814 putint(p, SV_GAMEINFO); 815 putint(p, game::numplayers); 816 entities::putitems(p); 817 putint(p, -1); 818 if(m_stf(game::gamemode)) stf::sendflags(p); 819 else if(m_ctf(game::gamemode)) ctf::sendflags(p); 820 sendinfo = false; 821 } 822 if(messages.length()) 823 { 824 p.put(messages.getbuf(), messages.length()); 825 messages.setsizenodelete(0); 826 if(messagereliable) p.reliable(); 827 messagereliable = false; 828 } 829 if(lastmillis-lastping>250) 830 { 831 putint(p, SV_PING); 832 putint(p, lastmillis); 833 lastping = lastmillis; 834 } 835 836 sendclientpacket(p.finalize(), 1); 837 } 838 c2sinfo()839 void c2sinfo() // send update to the server 840 { 841 static int lastupdate = -1000; 842 if(totalmillis-lastupdate < 40) return; // don't update faster than 25fps 843 lastupdate = totalmillis; 844 updateposition(game::player1); 845 loopv(game::players) if(game::players[i] && game::players[i]->ai) updateposition(game::players[i]); 846 sendmessages(game::player1); 847 flushclient(); 848 } 849 parsestate(gameent * d,ucharbuf & p,bool resume=false)850 void parsestate(gameent *d, ucharbuf &p, bool resume = false) 851 { 852 if(!d) { static gameent dummy; d = &dummy; } 853 if(d == game::player1 || d->ai) getint(p); 854 else d->state = getint(p); 855 d->frags = getint(p); 856 d->health = getint(p); 857 d->cptime = getint(p); 858 if(resume && (d == game::player1 || d->ai)) 859 { 860 d->weapreset(false); 861 getint(p); 862 loopi(WEAP_MAX) getint(p); 863 } 864 else 865 { 866 d->weapreset(true); 867 d->lastweap = d->weapselect = getint(p); 868 if(m_arena(game::gamemode, game::mutators)) d->arenaweap = d->weapselect; 869 loopi(WEAP_MAX) d->ammo[i] = getint(p); 870 } 871 } 872 updatepos(gameent * d)873 void updatepos(gameent *d) 874 { 875 // update the position of other clients in the game in our world 876 // don't care if he's in the scenery or other players, 877 // just don't overlap with our client 878 879 const float r = game::player1->radius+d->radius; 880 const float dx = game::player1->o.x-d->o.x; 881 const float dy = game::player1->o.y-d->o.y; 882 const float dz = game::player1->o.z-d->o.z; 883 const float rz = game::player1->aboveeye+game::player1->height; 884 const float fx = (float)fabs(dx), fy = (float)fabs(dy), fz = (float)fabs(dz); 885 if(fx<r && fy<r && fz<rz && d->state!=CS_SPECTATOR && d->state!=CS_WAITING && d->state!=CS_DEAD) 886 { 887 if(fx<fy) d->o.y += dy<0 ? r-fy : -(r-fy); // push aside 888 else d->o.x += dx<0 ? r-fx : -(r-fx); 889 } 890 int lagtime = lastmillis-d->lastupdate; 891 if(lagtime) 892 { 893 if(d->lastupdate) d->plag = (d->plag*5+lagtime)/6; 894 d->lastupdate = lastmillis; 895 } 896 } 897 parsepositions(ucharbuf & p)898 void parsepositions(ucharbuf &p) 899 { 900 int type; 901 while(p.remaining()) switch(type = getint(p)) 902 { 903 case SV_POS: // position of another client 904 { 905 int lcn = getint(p); 906 vec o, vel, falling; 907 float yaw, pitch, roll, aimyaw, aimpitch; 908 int physstate, f; 909 o.x = getuint(p)/DMF; 910 o.y = getuint(p)/DMF; 911 o.z = getuint(p)/DMF; 912 aimyaw = yaw = (float)getuint(p); 913 aimpitch = pitch = (float)getint(p); 914 roll = (float)getint(p); 915 vel.x = getint(p)/DVELF; 916 vel.y = getint(p)/DVELF; 917 vel.z = getint(p)/DVELF; 918 physstate = getuint(p); 919 falling = vec(0, 0, 0); 920 if(physstate&0x20) 921 { 922 falling.x = getint(p)/DVELF; 923 falling.y = getint(p)/DVELF; 924 } 925 if(physstate&0x10) falling.z = getint(p)/DVELF; 926 f = getuint(p); 927 if(f&0x20) 928 { 929 aimyaw = (float)getuint(p); 930 aimpitch = (float)getint(p); 931 } 932 gameent *d = game::getclient(lcn); 933 if(!d || d==game::player1 || d->ai) continue; 934 float oldyaw = d->yaw, oldpitch = d->pitch, oldaimyaw = d->aimyaw, oldaimpitch = d->aimpitch; 935 d->action[AC_IMPULSE] = FWV(impulsestyle) && f&0x40 ? true : false; 936 d->conopen = f&0x80 ? true : false; 937 d->yaw = yaw; 938 d->pitch = pitch; 939 d->roll = roll; 940 d->aimyaw = aimyaw; 941 d->aimpitch = aimpitch; 942 d->strafe = (f&3)==3 ? -1 : f&3; f >>= 2; 943 d->move = (f&3)==3 ? -1 : f&3; f >>= 2; 944 bool crouch = d->action[AC_CROUCH]; 945 d->action[AC_CROUCH] = f&1 ? true : false; 946 if(crouch != d->action[AC_CROUCH]) d->actiontime[AC_CROUCH] = lastmillis; 947 vec oldpos(d->o); 948 //if(game::allowmove(d)) 949 //{ 950 d->o = o; 951 d->o.z += d->height; 952 d->vel = vel; 953 d->falling = falling; 954 d->physstate = physstate & 0x0F; 955 physics::updatephysstate(d); 956 updatepos(d); 957 //} 958 if(d->state==CS_DEAD || d->state==CS_WAITING) 959 { 960 d->resetinterp(); 961 d->smoothmillis = 0; 962 } 963 else if(physics::smoothmove && d->smoothmillis>=0 && oldpos.dist(d->o) < physics::smoothdist) 964 { 965 d->newpos = d->o; 966 d->newpos.z -= d->height; 967 d->newyaw = d->yaw; 968 d->newpitch = d->pitch; 969 d->newaimyaw = d->aimyaw; 970 d->newaimpitch = d->aimpitch; 971 972 d->o = oldpos; 973 d->yaw = oldyaw; 974 d->pitch = oldpitch; 975 d->aimyaw = oldaimyaw; 976 d->aimpitch = oldaimpitch; 977 978 oldpos.z -= d->height; 979 (d->deltapos = oldpos).sub(d->newpos); 980 981 d->deltayaw = oldyaw - d->newyaw; 982 if(d->deltayaw > 180) d->deltayaw -= 360; 983 else if(d->deltayaw < -180) d->deltayaw += 360; 984 d->deltapitch = oldpitch - d->newpitch; 985 986 d->deltaaimyaw = oldaimyaw - d->newaimyaw; 987 if(d->deltaaimyaw > 180) d->deltaaimyaw -= 360; 988 else if(d->deltaaimyaw < -180) d->deltaaimyaw += 360; 989 d->deltaaimpitch = oldaimpitch - d->newaimpitch; 990 991 d->smoothmillis = lastmillis; 992 } 993 else d->smoothmillis = 0; 994 break; 995 } 996 997 default: 998 neterr("type"); 999 return; 1000 } 1001 } 1002 parsemessages(int cn,gameent * d,ucharbuf & p)1003 void parsemessages(int cn, gameent *d, ucharbuf &p) 1004 { 1005 static char text[MAXTRANS]; 1006 int type = -1, prevtype = -1; 1007 bool mapchanged = false; 1008 1009 while(p.remaining()) 1010 { 1011 prevtype = type; 1012 type = getint(p); 1013 if(verbose > 5) conoutf("[client] msg: %d, prev: %d", type, prevtype); 1014 switch(type) 1015 { 1016 case SV_SERVERINIT: // welcome messsage from the server 1017 { 1018 int mycn = getint(p), gver = getint(p); 1019 if(gver!=GAMEVERSION) 1020 { 1021 conoutft(CON_MESG, "\fryou are using a different game version (you: %d, server: %d)", GAMEVERSION, gver); 1022 disconnect(); 1023 return; 1024 } 1025 sessionid = getint(p); 1026 game::player1->clientnum = mycn; 1027 if(getint(p)) conoutft(CON_MESG, "\frthe server is password protected"); 1028 else if(verbose) conoutf("\fathe server welcomes us, yay"); 1029 sendintro(); 1030 break; 1031 } 1032 case SV_WELCOME: isready = true; break; 1033 1034 case SV_CLIENT: 1035 { 1036 int lcn = getint(p), len = getuint(p); 1037 ucharbuf q = p.subbuf(len); 1038 gameent *t = game::getclient(lcn); 1039 parsemessages(lcn, t, q); 1040 break; 1041 } 1042 1043 case SV_PHYS: // simple phys events 1044 { 1045 int lcn = getint(p), st = getint(p); 1046 gameent *t = game::getclient(lcn); 1047 if(t && t != game::player1 && !t->ai) switch(st) 1048 { 1049 case SPHY_JUMP: 1050 { 1051 playsound(S_JUMP, t->o, t); regularshape(PART_SMOKE, int(t->radius), 0x111111, 21, 20, 100, t->feetpos(), 1, 1, -10, 0, 10.f); 1052 t->actiontime[AC_JUMP] = lastmillis; 1053 break; 1054 } 1055 case SPHY_IMPULSE: 1056 { 1057 playsound(S_IMPULSE, t->o, t); game::impulseeffect(t, true); 1058 break; 1059 } 1060 case SPHY_POWER: 1061 { 1062 t->setweapstate(t->weapselect, WEAP_S_POWER, 0, lastmillis); 1063 break; 1064 } 1065 case SPHY_EXTINGUISH: 1066 { 1067 if(issound(t->fschan)) removesound(t->fschan); 1068 t->fschan = -1; t->lastfire = 0; 1069 playsound(S_EXTINGUISH, t->o, t, 0, 128); 1070 break; 1071 } 1072 default: break; 1073 } 1074 break; 1075 } 1076 1077 case SV_ANNOUNCE: 1078 { 1079 int snd = getint(p), targ = getint(p); 1080 getstring(text, p); 1081 if(targ >= 0 && text[0]) game::announce(snd, targ, NULL, "%s", text); 1082 else game::announce(snd, -1, NULL, ""); 1083 break; 1084 } 1085 1086 case SV_TEXT: 1087 { 1088 int tcn = getint(p); 1089 gameent *t = game::getclient(tcn); 1090 int flags = getint(p); 1091 getstring(text, p); 1092 if(!t) break; 1093 saytext(t, flags, text); 1094 break; 1095 } 1096 1097 case SV_COMMAND: 1098 { 1099 int lcn = getint(p); 1100 gameent *f = game::getclient(lcn); 1101 string cmd; 1102 getstring(cmd, p); 1103 getstring(text, p); 1104 parsecommand(f ? f : NULL, cmd, text); 1105 break; 1106 } 1107 1108 case SV_EXECLINK: 1109 { 1110 int tcn = getint(p), index = getint(p); 1111 gameent *t = game::getclient(tcn); 1112 if(!t || !d || (t->clientnum != d->clientnum && t->ownernum != d->clientnum) || t == game::player1 || t->ai) break; 1113 entities::execlink(t, index, false); 1114 break; 1115 } 1116 1117 case SV_MAPCHANGE: 1118 { 1119 int hasmap = getint(p); 1120 if(hasmap) getstring(text, p); 1121 int getit = getint(p), mode = getint(p), muts = getint(p); 1122 changemapserv(hasmap && getit != 1 ? text : NULL, mode, muts, getit == 2); 1123 mapchanged = true; 1124 if(needsmap && multiplayer(false)) 1125 { 1126 switch(getit) 1127 { 1128 case 0: 1129 { 1130 conoutf("\fcserver requested map change to %s, and we need it, so asking for it", hasmap ? text : "<temp>"); 1131 addmsg(SV_GETMAP, "r"); 1132 break; 1133 } 1134 case 2: 1135 { 1136 conoutf("\fcseem to have failed to get map to %s, try /getmap", hasmap ? text : "<temp>"); 1137 needsmap = false; // we failed sir 1138 break; 1139 } 1140 } 1141 } 1142 else needsmap = false; // assume empty map 1143 break; 1144 } 1145 1146 case SV_GAMEINFO: 1147 { 1148 int n; 1149 while((n = getint(p)) != -1) entities::setspawn(n, getint(p)); 1150 sendinfo = false; 1151 break; 1152 } 1153 1154 case SV_NEWGAME: // server requests next game 1155 { 1156 hud::sb.showscores(false); 1157 if(!menuactive()) showgui("game"); 1158 break; 1159 } 1160 1161 case SV_SWITCHNAME: 1162 getstring(text, p); 1163 if(!d) break; 1164 filtertext(text, text, true, MAXNAMELEN); 1165 if(!text[0]) copystring(text, "unnamed"); 1166 if(strcmp(d->name, text)) 1167 { 1168 string oldname, newname; 1169 copystring(oldname, game::colorname(d, NULL, "", false)); 1170 copystring(newname, game::colorname(d, text)); 1171 if(game::showplayerinfo) 1172 conoutft(game::showplayerinfo > 1 ? int(CON_EVENT) : int(CON_MESG), "\fm%s is now known as %s", oldname, newname); 1173 } 1174 copystring(d->name, text, MAXNAMELEN+1); 1175 break; 1176 1177 case SV_CLIENTINIT: // another client either connected or changed name/team 1178 { 1179 int cn = getint(p); 1180 gameent *d = game::newclient(cn); 1181 if(!d) 1182 { 1183 getstring(text, p); 1184 getint(p); 1185 break; 1186 } 1187 getstring(text, p); 1188 filtertext(text, text, true, MAXNAMELEN); 1189 if(!text[0]) copystring(text, "unnamed"); 1190 if(d->name[0]) // already connected 1191 { 1192 if(strcmp(d->name, text)) 1193 { 1194 string oldname, newname; 1195 copystring(oldname, game::colorname(d, NULL, "", false)); 1196 copystring(newname, game::colorname(d, text)); 1197 if(game::showplayerinfo) 1198 conoutft(game::showplayerinfo > 1 ? int(CON_EVENT) : int(CON_MESG), "\fm%s is now known as %s", oldname, newname); 1199 } 1200 } 1201 else // new client 1202 { 1203 if(game::showplayerinfo) 1204 conoutft(game::showplayerinfo > 1 ? int(CON_EVENT) : int(CON_MESG), "\fg%s has joined the game", game::colorname(d, text, "", false)); 1205 loopv(game::players) // clear copies since new player doesn't have them 1206 if(game::players[i]) freeeditinfo(game::players[i]->edit); 1207 freeeditinfo(localedit); 1208 } 1209 copystring(d->name, text, MAXNAMELEN+1); 1210 int team = clamp(getint(p), int(TEAM_NEUTRAL), int(TEAM_ENEMY)); 1211 if(d == game::player1 && d->team != team) hud::lastteam = 0; 1212 d->team = team; 1213 break; 1214 } 1215 1216 case SV_DISCONNECT: 1217 game::clientdisconnected(getint(p)); 1218 break; 1219 1220 case SV_SPAWN: 1221 { 1222 int lcn = getint(p); 1223 gameent *f = game::newclient(lcn); 1224 if(f && f != game::player1 && !f->ai) 1225 { 1226 f->respawn(lastmillis, m_health(game::gamemode, game::mutators)); 1227 parsestate(f, p); 1228 playsound(S_RESPAWN, f->o, f); 1229 game::spawneffect(PART_FIREBALL, vec(f->o).sub(vec(0, 0, f->height/2.f)), teamtype[f->team].colour, int(f->radius*2)); 1230 } 1231 else parsestate(NULL, p); 1232 break; 1233 } 1234 1235 case SV_ARENAWEAP: 1236 { 1237 hud::sb.showscores(false); 1238 if(!menuactive()) showgui("arena"); 1239 break; 1240 } 1241 1242 case SV_SPAWNSTATE: 1243 { 1244 int lcn = getint(p), ent = getint(p); 1245 gameent *f = game::newclient(lcn); 1246 if(!f) 1247 { 1248 parsestate(NULL, p); 1249 break; 1250 } 1251 if(f == game::player1 && editmode) toggleedit(); 1252 f->respawn(lastmillis, m_health(game::gamemode, game::mutators)); 1253 parsestate(f, p); 1254 f->state = CS_ALIVE; 1255 if(f == game::player1 || f->ai) 1256 { 1257 addmsg(SV_SPAWN, "ri", f->clientnum); 1258 entities::spawnplayer(f, ent, true); 1259 playsound(S_RESPAWN, f->o, f); 1260 game::spawneffect(PART_FIREBALL, vec(f->o).sub(vec(0, 0, f->height/2.f)), teamtype[f->team].colour, int(f->radius*2)); 1261 } 1262 ai::spawned(f, ent); 1263 if(f == game::player1) game::resetcamera(); 1264 break; 1265 } 1266 1267 case SV_SHOTFX: 1268 { 1269 int scn = getint(p), weap = getint(p), flags = getint(p), power = getint(p); 1270 vec from; 1271 loopk(3) from[k] = getint(p)/DMF; 1272 int ls = getint(p); 1273 vector<vec> locs; 1274 loopj(ls) 1275 { 1276 vec &to = locs.add(); 1277 loopk(3) to[k] = getint(p)/DMF; 1278 } 1279 gameent *s = game::getclient(scn); 1280 if(!s || !isweap(weap) || s == game::player1 || s->ai) break; 1281 s->setweapstate(weap, WEAP_S_SHOOT, weaptype[weap].adelay[flags&HIT_ALT ? 1 : 0], lastmillis); 1282 s->weapshot[weap] = weaptype[weap].sub[flags&HIT_ALT ? 1 : 0]; 1283 s->totalshots += int(weaptype[weap].damage[flags&HIT_ALT ? 1 : 0]*damagescale)*weaptype[weap].rays[flags&HIT_ALT ? 1 : 0]; 1284 projs::shootv(weap, flags, power, from, locs, s, false); 1285 break; 1286 } 1287 1288 case SV_DAMAGE: 1289 { 1290 int tcn = getint(p), 1291 acn = getint(p), 1292 weap = getint(p), 1293 flags = getint(p), 1294 damage = getint(p), 1295 health = getint(p); 1296 vec dir; 1297 loopk(3) dir[k] = getint(p)/DNF; 1298 dir.normalize(); 1299 gameent *target = game::getclient(tcn), *actor = game::getclient(acn); 1300 if(!target || !actor) break; 1301 game::damaged(weap, flags, damage, health, target, actor, lastmillis, dir); 1302 break; 1303 } 1304 1305 case SV_RELOAD: 1306 { 1307 int trg = getint(p), weap = getint(p), amt = getint(p), ammo = getint(p); 1308 gameent *target = game::getclient(trg); 1309 if(!target || !isweap(weap)) break; 1310 weapons::weapreload(target, weap, amt, ammo, false); 1311 break; 1312 } 1313 1314 case SV_REGEN: 1315 { 1316 int trg = getint(p), heal = getint(p), amt = getint(p); 1317 gameent *f = game::getclient(trg); 1318 if(!f) break; 1319 if(!amt) 1320 { 1321 f->impulse[IM_METER] = 0; 1322 if(issound(f->fschan)) removesound(f->fschan); 1323 f->fschan = -1; f->lastfire = 0; 1324 } 1325 else if(amt > 0 && (!f->lastregen || lastmillis-f->lastregen >= 500)) playsound(S_REGEN, f->o, f); 1326 f->health = heal; f->lastregen = lastmillis; 1327 break; 1328 } 1329 1330 case SV_DIED: 1331 { 1332 int vcn = getint(p), acn = getint(p), frags = getint(p), style = getint(p), weap = getint(p), flags = getint(p), damage = getint(p); 1333 gameent *victim = game::getclient(vcn), *actor = game::getclient(acn); 1334 if(!actor || !victim) break; 1335 actor->frags = frags; 1336 game::killed(weap, flags, damage, victim, actor, style); 1337 victim->lastdeath = lastmillis; 1338 victim->weapreset(true); 1339 break; 1340 } 1341 1342 case SV_POINTS: 1343 { 1344 int acn = getint(p), add = getint(p), points = getint(p); 1345 gameent *actor = game::getclient(acn); 1346 if(!actor) break; 1347 actor->lastpoints = add; 1348 actor->points = points; 1349 break; 1350 } 1351 1352 case SV_DROP: 1353 { 1354 int trg = getint(p), weap = getint(p), ds = getint(p); 1355 gameent *target = game::getclient(trg); 1356 bool local = target && (target == game::player1 || target->ai); 1357 if(ds) loopj(ds) 1358 { 1359 int gs = getint(p), drop = getint(p); 1360 if(target) projs::drop(target, gs, drop, local); 1361 } 1362 if(isweap(weap) && target) 1363 { 1364 target->weapswitch(weap, lastmillis); 1365 if(issound(target->wschan)) removesound(target->wschan); 1366 playsound(S_SWITCH, target->o, target, 0, -1, -1, -1, &target->wschan); 1367 } 1368 break; 1369 } 1370 1371 case SV_WEAPSELECT: 1372 { 1373 int trg = getint(p), weap = getint(p); 1374 gameent *target = game::getclient(trg); 1375 if(!target || !isweap(weap)) break; 1376 weapons::weapselect(target, weap, false); 1377 break; 1378 } 1379 1380 case SV_RESUME: 1381 { 1382 for(;;) 1383 { 1384 int lcn = getint(p); 1385 if(p.overread() || lcn < 0) break; 1386 gameent *f = game::newclient(lcn); 1387 if(f && f!=game::player1 && !f->ai) f->respawn(0, m_health(game::gamemode, game::mutators)); 1388 parsestate(f, p, true); 1389 } 1390 break; 1391 } 1392 1393 case SV_ITEMSPAWN: 1394 { 1395 int ent = getint(p); 1396 if(!entities::ents.inrange(ent)) break; 1397 entities::setspawn(ent, 1); 1398 playsound(S_ITEMSPAWN, entities::ents[ent]->o); 1399 int sweap = m_weapon(game::gamemode, game::mutators), attr = entities::ents[ent]->type == WEAPON ? w_attr(game::gamemode, entities::ents[ent]->attrs[0], sweap) : entities::ents[ent]->attrs[0], 1400 colour = entities::ents[ent]->type == WEAPON ? weaptype[attr].colour : 0xFFFFFF; 1401 if(entities::showentdescs) 1402 { 1403 vec pos = vec(entities::ents[ent]->o).add(vec(0, 0, 4)); 1404 const char *texname = entities::showentdescs >= 2 ? hud::itemtex(entities::ents[ent]->type, attr) : NULL; 1405 if(texname && *texname) part_icon(pos, textureload(texname, 3), 2, 1, -10, 0, game::aboveheadfade, colour); 1406 else 1407 { 1408 const char *item = entities::entinfo(entities::ents[ent]->type, entities::ents[ent]->attrs, false); 1409 if(item && *item) 1410 { 1411 defformatstring(ds)("<emphasis>%s (%d)", item, entities::ents[ent]->type); 1412 part_textcopy(pos, ds, PART_TEXT, game::aboveheadfade, colour, 2, 1, -10); 1413 } 1414 } 1415 } 1416 game::spawneffect(PART_FIREBALL, entities::ents[ent]->o, entities::ents[ent]->type == WEAPON ? colour : 0x6666FF, enttype[entities::ents[ent]->type].radius, 5); 1417 break; 1418 } 1419 1420 case SV_TRIGGER: 1421 { 1422 int ent = getint(p), st = getint(p); 1423 entities::setspawn(ent, st); 1424 break; 1425 } 1426 1427 case SV_ITEMACC: 1428 { // uses a specific drop so the client knows what to replace 1429 int lcn = getint(p), ent = getint(p), spawn = getint(p), weap = getint(p), drop = getint(p); 1430 gameent *target = game::getclient(lcn); 1431 if(!target) break; 1432 if(entities::ents.inrange(ent) && enttype[entities::ents[ent]->type].usetype == EU_ITEM) 1433 entities::useeffects(target, ent, spawn, weap, drop); 1434 break; 1435 } 1436 1437 case SV_EDITVAR: 1438 { 1439 int t = getint(p); 1440 bool commit = true; 1441 getstring(text, p); 1442 ident *id = idents->access(text); 1443 if(!d || !id || !(id->flags&IDF_WORLD) || id->type != t) commit = false; 1444 switch(t) 1445 { 1446 case ID_VAR: 1447 { 1448 int val = getint(p); 1449 if(commit) 1450 { 1451 if(val > id->maxval) val = id->maxval; 1452 else if(val < id->minval) val = id->minval; 1453 setvar(text, val, true); 1454 defformatstring(str)(id->flags&IDF_HEX ? (id->maxval==0xFFFFFF ? "0x%.6X" : "0x%X") : "%d", *id->storage.i); 1455 conoutft(CON_MESG, "\fg%s set worldvar %s to %s", game::colorname(d), id->name, str); 1456 } 1457 break; 1458 } 1459 case ID_FVAR: 1460 { 1461 float val = getfloat(p); 1462 if(commit) 1463 { 1464 if(val > id->maxvalf) val = id->maxvalf; 1465 else if(val < id->minvalf) val = id->minvalf; 1466 setfvar(text, val, true); 1467 conoutft(CON_MESG, "\fg%s set worldvar %s to %s", game::colorname(d), id->name, floatstr(*id->storage.f)); 1468 } 1469 break; 1470 } 1471 case ID_SVAR: 1472 { 1473 string val; 1474 getstring(val, p); 1475 if(commit) 1476 { 1477 setsvar(text, val, true); 1478 conoutft(CON_MESG, "\fg%s set worldvar %s to %s", game::colorname(d), id->name, *id->storage.s); 1479 } 1480 break; 1481 } 1482 case ID_ALIAS: 1483 { 1484 string val; 1485 getstring(val, p); 1486 if(commit || !id) // set aliases anyway 1487 { 1488 worldalias(text, val); 1489 conoutft(CON_MESG, "\fg%s set worldalias %s to %s", game::colorname(d), text, val); 1490 } 1491 break; 1492 } 1493 default: break; 1494 } 1495 break; 1496 } 1497 1498 case SV_EDITF: // coop editing messages 1499 case SV_EDITT: 1500 case SV_EDITM: 1501 case SV_FLIP: 1502 case SV_COPY: 1503 case SV_PASTE: 1504 case SV_ROTATE: 1505 case SV_REPLACE: 1506 case SV_DELCUBE: 1507 { 1508 if(!d) return; 1509 selinfo s; 1510 s.o.x = getint(p); s.o.y = getint(p); s.o.z = getint(p); 1511 s.s.x = getint(p); s.s.y = getint(p); s.s.z = getint(p); 1512 s.grid = getint(p); s.orient = getint(p); 1513 s.cx = getint(p); s.cxs = getint(p); s.cy = getint(p), s.cys = getint(p); 1514 s.corner = getint(p); 1515 int dir, mode, tex, newtex, mat, allfaces; 1516 ivec moveo; 1517 switch(type) 1518 { 1519 case SV_EDITF: dir = getint(p); mode = getint(p); mpeditface(dir, mode, s, false); break; 1520 case SV_EDITT: tex = getint(p); allfaces = getint(p); mpedittex(tex, allfaces, s, false); break; 1521 case SV_EDITM: mat = getint(p); mpeditmat(mat, s, false); break; 1522 case SV_FLIP: mpflip(s, false); break; 1523 case SV_COPY: if(d) mpcopy(d->edit, s, false); break; 1524 case SV_PASTE: if(d) mppaste(d->edit, s, false); break; 1525 case SV_ROTATE: dir = getint(p); mprotate(dir, s, false); break; 1526 case SV_REPLACE: tex = getint(p); newtex = getint(p); mpreplacetex(tex, newtex, s, false); break; 1527 case SV_DELCUBE: mpdelcube(s, false); break; 1528 } 1529 break; 1530 } 1531 case SV_REMIP: 1532 { 1533 if(!d) return; 1534 conoutft(CON_MESG, "%s remipped", game::colorname(d)); 1535 mpremip(false); 1536 break; 1537 } 1538 case SV_EDITENT: // coop edit of ent 1539 { 1540 if(!d) return; 1541 int i = getint(p); 1542 float x = getint(p)/DMF, y = getint(p)/DMF, z = getint(p)/DMF; 1543 int type = getint(p), numattrs = getint(p); 1544 static vector<int> attrs; attrs.setsizenodelete(0); 1545 loopk(numattrs) attrs.add(getint(p)); 1546 mpeditent(i, vec(x, y, z), type, attrs, false); 1547 entities::setspawn(i, 0); 1548 entities::clearentcache(); 1549 break; 1550 } 1551 1552 case SV_EDITLINK: 1553 { 1554 if(!d) return; 1555 int b = getint(p), index = getint(p), node = getint(p); 1556 entities::linkents(index, node, b!=0, false, false); 1557 } 1558 1559 case SV_PONG: 1560 addmsg(SV_CLIENTPING, "i", game::player1->ping = (game::player1->ping*5+lastmillis-getint(p))/6); 1561 break; 1562 1563 case SV_CLIENTPING: 1564 if(!d) return; 1565 d->ping = getint(p); 1566 break; 1567 1568 case SV_TIMEUP: 1569 game::timeupdate(getint(p)); 1570 break; 1571 1572 case SV_SERVMSG: 1573 { 1574 int lev = getint(p); 1575 getstring(text, p); 1576 conoutft(lev >= 0 && lev < CON_MAX ? lev : CON_INFO, "%s", text); 1577 break; 1578 } 1579 1580 case SV_SENDDEMOLIST: 1581 { 1582 int demos = getint(p); 1583 if(!demos) conoutf("\frno demos available"); 1584 else loopi(demos) 1585 { 1586 getstring(text, p); 1587 conoutf("\fa%d. %s", i+1, text); 1588 } 1589 break; 1590 } 1591 1592 case SV_DEMOPLAYBACK: 1593 { 1594 int on = getint(p); 1595 if(on) game::player1->state = CS_SPECTATOR; 1596 else 1597 { 1598 loopv(game::players) if(game::players[i]) game::clientdisconnected(i); 1599 } 1600 demoplayback = on!=0; 1601 game::player1->clientnum = getint(p); 1602 break; 1603 } 1604 1605 case SV_CURRENTMASTER: 1606 { 1607 int mn = getint(p), priv = getint(p); 1608 if(mn >= 0) 1609 { 1610 gameent *m = game::getclient(mn); 1611 if(m) m->privilege = priv; 1612 } 1613 break; 1614 } 1615 1616 case SV_EDITMODE: 1617 { 1618 int val = getint(p); 1619 if(!d) break; 1620 if(val) d->state = CS_EDITING; 1621 else 1622 { 1623 d->state = CS_ALIVE; 1624 d->editspawn(lastmillis, m_weapon(game::gamemode, game::mutators), m_health(game::gamemode, game::mutators), m_arena(game::gamemode, game::mutators), spawngrenades >= (m_insta(game::gamemode, game::mutators) ? 2 : 1)); 1625 } 1626 d->resetinterp(); 1627 projs::remove(d); 1628 break; 1629 } 1630 1631 case SV_SPECTATOR: 1632 { 1633 int sn = getint(p), val = getint(p); 1634 gameent *s = game::newclient(sn); 1635 if(!s) break; 1636 if(val) 1637 { 1638 if(s == game::player1 && editmode) toggleedit(); 1639 s->state = CS_SPECTATOR; 1640 s->checkpoint = -1; 1641 s->cpmillis = 0; 1642 } 1643 else if(s->state == CS_SPECTATOR) 1644 { 1645 s->state = CS_WAITING; 1646 s->checkpoint = -1; 1647 s->cpmillis = 0; 1648 if(s != game::player1 && !s->ai) s->resetinterp(); 1649 } 1650 break; 1651 } 1652 1653 case SV_WAITING: 1654 { 1655 int sn = getint(p); 1656 gameent *s = game::newclient(sn); 1657 if(!s) break; 1658 if(s == game::player1) 1659 { 1660 if(editmode) toggleedit(); 1661 hud::sb.showscores(false); 1662 } 1663 else if(!s->ai) s->resetinterp(); 1664 if(s->state == CS_ALIVE) s->lastdeath = lastmillis; // so spawndelay shows properly 1665 s->state = CS_WAITING; 1666 s->weapreset(true); 1667 break; 1668 } 1669 1670 case SV_SETTEAM: 1671 { 1672 int wn = getint(p), tn = getint(p); 1673 gameent *w = game::getclient(wn); 1674 if(!w) return; 1675 if(w == game::player1 && w->team != tn) hud::lastteam = 0; 1676 w->team = tn; 1677 break; 1678 } 1679 1680 case SV_FLAGINFO: 1681 { 1682 int flag = getint(p), converted = getint(p), 1683 owner = getint(p), enemy = getint(p); 1684 if(m_stf(game::gamemode)) stf::updateflag(flag, owner, enemy, converted); 1685 break; 1686 } 1687 1688 case SV_FLAGS: 1689 { 1690 int numflags = getint(p); 1691 loopi(numflags) 1692 { 1693 int converted = getint(p), owner = getint(p), enemy = getint(p); 1694 stf::st.initflag(i, owner, enemy, converted); 1695 } 1696 break; 1697 } 1698 1699 case SV_MAPVOTE: 1700 { 1701 int vn = getint(p); 1702 gameent *v = game::getclient(vn); 1703 getstring(text, p); 1704 filtertext(text, text); 1705 int reqmode = getint(p), reqmuts = getint(p); 1706 if(!v) break; 1707 vote(v, text, reqmode, reqmuts); 1708 break; 1709 } 1710 1711 case SV_CHECKPOINT: 1712 { 1713 int tn = getint(p), laptime = getint(p), besttime = getint(p); 1714 gameent *t = game::getclient(tn); 1715 if(!t) break; 1716 t->cplast = laptime; 1717 t->cptime = besttime; 1718 t->cpmillis = t->impulse[IM_METER] = 0; 1719 if(showlaptimes > (t != game::player1 ? (t->aitype >= 0 ? 2 : 1) : 0)) 1720 { 1721 defformatstring(best)("%s", hud::sb.timetostr(besttime)); 1722 conoutft(t != game::player1 ? CON_INFO : CON_SELF, "%s lap time: \fs\fg%s\fS (best: \fs\fy%s\fS)", game::colorname(t), hud::sb.timetostr(laptime), best); 1723 } 1724 } 1725 1726 case SV_SCORE: 1727 { 1728 int team = getint(p), total = getint(p); 1729 if(m_stf(game::gamemode)) stf::setscore(team, total); 1730 else if(m_ctf(game::gamemode)) ctf::setscore(team, total); 1731 break; 1732 } 1733 1734 case SV_INITFLAGS: 1735 { 1736 ctf::parseflags(p, m_ctf(game::gamemode)); 1737 break; 1738 } 1739 1740 case SV_DROPFLAG: 1741 { 1742 int ocn = getint(p), flag = getint(p); 1743 vec droploc; 1744 loopk(3) droploc[k] = getint(p)/DMF; 1745 gameent *o = game::newclient(ocn); 1746 if(o && m_ctf(game::gamemode)) ctf::dropflag(o, flag, droploc); 1747 break; 1748 } 1749 1750 case SV_SCOREFLAG: 1751 { 1752 int ocn = getint(p), relayflag = getint(p), goalflag = getint(p), score = getint(p); 1753 gameent *o = game::newclient(ocn); 1754 if(o && m_ctf(game::gamemode)) ctf::scoreflag(o, relayflag, goalflag, score); 1755 break; 1756 } 1757 1758 case SV_RETURNFLAG: 1759 { 1760 int ocn = getint(p), flag = getint(p); 1761 gameent *o = game::newclient(ocn); 1762 if(o && m_ctf(game::gamemode)) ctf::returnflag(o, flag); 1763 break; 1764 } 1765 1766 case SV_TAKEFLAG: 1767 { 1768 int ocn = getint(p), flag = getint(p); 1769 gameent *o = game::newclient(ocn); 1770 if(o && m_ctf(game::gamemode)) ctf::takeflag(o, flag); 1771 break; 1772 } 1773 1774 case SV_RESETFLAG: 1775 { 1776 int flag = getint(p); 1777 if(m_ctf(game::gamemode)) ctf::resetflag(flag); 1778 break; 1779 } 1780 1781 case SV_GETMAP: 1782 { 1783 conoutf("\fcserver has requested we send the map.."); 1784 if(!needsmap && !gettingmap) sendmap(); 1785 else 1786 { 1787 if(!gettingmap) conoutf("\frwe don't have the map though, so asking for it instead"); 1788 else conoutf("\frbut we're in the process of getting it"); 1789 addmsg(SV_GETMAP, "r"); 1790 } 1791 break; 1792 } 1793 1794 case SV_SENDMAP: 1795 { 1796 conoutf("\fcmap data has been uploaded"); 1797 if(needsmap && !gettingmap) 1798 { 1799 conoutf("\frwe want the map too, so asking for it"); 1800 addmsg(SV_GETMAP, "r"); 1801 } 1802 break; 1803 } 1804 1805 case SV_NEWMAP: 1806 { 1807 int size = getint(p); 1808 if(size>=0) emptymap(size, true); 1809 else enlargemap(true); 1810 mapvotes.setsize(0); 1811 needsmap = false; 1812 if(d && d!=game::player1) 1813 { 1814 int newsize = 0; 1815 while(1<<newsize < getworldsize()) newsize++; 1816 conoutft(CON_MESG, size>=0 ? "%s started a new map of size %d" : "%s enlarged the map to size %d", game::colorname(d), newsize); 1817 } 1818 break; 1819 } 1820 1821 case SV_INITAI: 1822 { 1823 int bn = getint(p), on = getint(p), at = getint(p), et = getint(p), sk = clamp(getint(p), 1, 101); 1824 getstring(text, p); 1825 int tm = getint(p); 1826 gameent *b = game::newclient(bn); 1827 if(!b) break; 1828 ai::init(b, at, et, on, sk, bn, text, tm); 1829 break; 1830 } 1831 1832 case SV_AUTHCHAL: 1833 { 1834 uint id = (uint)getint(p); 1835 getstring(text, p); 1836 conoutft(CON_MESG, "server is challenging authentication details.."); 1837 if(lastauth && lastmillis-lastauth < 60*1000 && authname[0]) 1838 { 1839 vector<char> buf; 1840 answerchallenge(authkey, text, buf); 1841 addmsg(SV_AUTHANS, "ris", id, buf.getbuf()); 1842 } 1843 break; 1844 } 1845 1846 default: 1847 { 1848 neterr("type"); 1849 return; 1850 } 1851 } 1852 } 1853 } 1854 serverstat(serverinfo * a)1855 int serverstat(serverinfo *a) 1856 { 1857 if(a->attr.length() > 4 && a->numplayers >= a->attr[4]) 1858 { 1859 return SSTAT_FULL; 1860 } 1861 else if(a->attr.length() > 5) switch(a->attr[5]) 1862 { 1863 case MM_LOCKED: 1864 { 1865 return SSTAT_LOCKED; 1866 } 1867 case MM_PRIVATE: 1868 case MM_PASSWORD: 1869 { 1870 return SSTAT_PRIVATE; 1871 } 1872 default: 1873 { 1874 return SSTAT_OPEN; 1875 } 1876 } 1877 return SSTAT_UNKNOWN; 1878 } 1879 resetserversort()1880 void resetserversort() 1881 { 1882 defformatstring(u)("serversort [%d %d %d]", SINFO_STATUS, SINFO_PLAYERS, SINFO_PING); 1883 execute(u); 1884 } 1885 ICOMMAND(serversortreset, "", (), resetserversort()); 1886 servercompare(serverinfo * a,serverinfo * b)1887 int servercompare(serverinfo *a, serverinfo *b) 1888 { 1889 if(!serversort || !*serversort) resetserversort(); 1890 int ac = a->address.host == ENET_HOST_ANY || a->ping >= 999 || a->attr.empty() || a->attr[0] != GAMEVERSION ? -1 : 0, 1891 bc = b->address.host == ENET_HOST_ANY || b->ping >= 999 || b->attr.empty() || b->attr[0] != GAMEVERSION ? -1 : 0; 1892 if(!ac) 1893 { 1894 if(!strcmp(a->sdesc, servermaster)) ac = 3; 1895 else if(!strcmp(a->name, "localhost")) ac = 2; 1896 else ac = 1; 1897 } 1898 1899 if(!bc) 1900 { 1901 if(!strcmp(b->sdesc, servermaster)) bc = 3; 1902 else if(!strcmp(b->name, "localhost")) bc = 2; 1903 else bc = 1; 1904 } 1905 if(ac > bc) return -1; 1906 if(ac < bc) return 1; 1907 1908 #define retcp(c) if(c) { return c; } 1909 #define retsw(c,d,e) \ 1910 if(c != d) \ 1911 { \ 1912 if(e) { return c > d ? 1 : -1; } \ 1913 else { return c < d ? 1 : -1; } \ 1914 } 1915 1916 int len = execute("listlen $serversort"); 1917 1918 loopi(len) 1919 { 1920 defformatstring(s)("at $serversort %d", i); 1921 1922 int style = execute(s); 1923 serverinfo *aa = a, *ab = b; 1924 1925 if(style < 0) 1926 { 1927 style = 0-style; 1928 aa = b; 1929 ab = a; 1930 } 1931 1932 switch (style) 1933 { 1934 case SINFO_STATUS: 1935 { 1936 retsw(serverstat(aa), serverstat(ab), true); 1937 break; 1938 } 1939 case SINFO_DESC: 1940 { 1941 retcp(strcmp(aa->sdesc, ab->sdesc)); 1942 break; 1943 } 1944 case SINFO_PING: 1945 { 1946 retsw(aa->ping, ab->ping, true); 1947 break; 1948 } 1949 case SINFO_PLAYERS: 1950 { 1951 retsw(aa->numplayers, ab->numplayers, false); 1952 break; 1953 } 1954 case SINFO_MAXCLIENTS: 1955 { 1956 if(aa->attr.length() > 4) ac = aa->attr[4]; 1957 else ac = 0; 1958 1959 if(ab->attr.length() > 4) bc = ab->attr[4]; 1960 else bc = 0; 1961 1962 retsw(ac, bc, false); 1963 break; 1964 } 1965 case SINFO_GAME: 1966 { 1967 if(aa->attr.length() > 1) ac = aa->attr[1]; 1968 else ac = 0; 1969 1970 if(ab->attr.length() > 1) bc = ab->attr[1]; 1971 else bc = 0; 1972 1973 retsw(ac, bc, true); 1974 1975 if(aa->attr.length() > 2) ac = aa->attr[2]; 1976 else ac = 0; 1977 1978 if(ab->attr.length() > 2) bc = ab->attr[2]; 1979 else bc = 0; 1980 1981 retsw(ac, bc, true); 1982 break; 1983 } 1984 case SINFO_MAP: 1985 { 1986 retcp(strcmp(aa->map, ab->map)); 1987 break; 1988 } 1989 case SINFO_TIME: 1990 { 1991 if(aa->attr.length() > 3) ac = aa->attr[3]; 1992 else ac = 0; 1993 1994 if(ab->attr.length() > 3) bc = ab->attr[3]; 1995 else bc = 0; 1996 1997 retsw(ac, bc, false); 1998 break; 1999 } 2000 default: 2001 break; 2002 } 2003 } 2004 return strcmp(a->name, b->name); 2005 } 2006 parsepacketclient(int chan,packetbuf & p)2007 void parsepacketclient(int chan, packetbuf &p) // processes any updates from the server 2008 { 2009 switch(chan) 2010 { 2011 case 0: 2012 parsepositions(p); 2013 break; 2014 2015 case 1: 2016 parsemessages(-1, NULL, p); 2017 break; 2018 2019 case 2: 2020 receivefile(p.buf, p.maxlen); 2021 break; 2022 } 2023 } 2024 serverstartcolumn(guient * g,int i)2025 void serverstartcolumn(guient *g, int i) 2026 { 2027 g->pushlist(); 2028 if(g->buttonf("%s ", 0xFFFFFF, NULL, true, i ? serverinfotypes[i] : "") & GUI_UP) 2029 { 2030 mkstring(st); 2031 bool invert = false; 2032 int len = execute("listlen $serversort"); 2033 loopk(len) 2034 { 2035 defformatstring(s)("at $serversort %d", k); 2036 2037 int n = execute(s); 2038 if(abs(n) != i) 2039 { 2040 defformatstring(t)("%s%d", st[0] ? " " : "", n); 2041 formatstring(st)("%s%s", st[0] ? st : "", t); 2042 } 2043 else if(!k) invert = true; 2044 } 2045 defformatstring(u)("serversort [%d%s%s]", 2046 invert ? 0-i : i, st[0] ? " " : "", st[0] ? st : ""); 2047 execute(u); 2048 } 2049 2050 g->mergehits(true); 2051 } 2052 serverendcolumn(guient * g,int i)2053 void serverendcolumn(guient *g, int i) 2054 { 2055 g->mergehits(false); 2056 g->poplist(); 2057 } 2058 serverentry(guient * g,int i,serverinfo * si)2059 bool serverentry(guient *g, int i, serverinfo *si) 2060 { 2061 mkstring(text); 2062 int status = serverstat(si), colour = serverstatus[status].colour; 2063 if(status == SSTAT_OPEN && !strcmp(si->sdesc, servermaster)) colour |= 0x222222; 2064 switch(i) 2065 { 2066 case SINFO_STATUS: 2067 { 2068 if(g->button("", colour, serverstatus[status].icon) & GUI_UP) 2069 return true; 2070 break; 2071 } 2072 case SINFO_DESC: 2073 { 2074 copystring(text, si->sdesc, 24); 2075 if(g->buttonf("%s ", colour, NULL, true, text) & GUI_UP) return true; 2076 break; 2077 } 2078 case SINFO_PING: 2079 { 2080 formatstring(text)("%d", si->ping); 2081 if(g->buttonf("%s ", colour, NULL, true, text) & GUI_UP) return true; 2082 break; 2083 } 2084 case SINFO_PLAYERS: 2085 { 2086 formatstring(text)("%d", si->numplayers); 2087 if(g->buttonf("%s ", colour, NULL, true, text) & GUI_UP) return true; 2088 break; 2089 } 2090 case SINFO_MAXCLIENTS: 2091 { 2092 if(si->attr.length() > 4 && si->attr[4] >= 0) formatstring(text)("%d", si->attr[4]); 2093 if(g->buttonf("%s ", colour, NULL, true, text) & GUI_UP) return true; 2094 break; 2095 } 2096 case SINFO_GAME: 2097 { 2098 if(si->attr.length() > 1) formatstring(text)("%s", server::gamename(si->attr[1], si->attr[2])); 2099 if(g->buttonf("%s ", colour, NULL, true, text) & GUI_UP) return true; 2100 break; 2101 } 2102 case SINFO_MAP: 2103 { 2104 copystring(text, si->map, 18); 2105 if(g->buttonf("%s ", colour, NULL, true, text) & GUI_UP) return true; 2106 break; 2107 } 2108 case SINFO_TIME: 2109 { 2110 if(si->attr.length() > 3 && si->attr[3] >= 0) 2111 formatstring(text)("%d %s", si->attr[3], si->attr[3] == 1 ? "min" : "mins"); 2112 if(g->buttonf("%s ", colour, NULL, true, text) & GUI_UP) return true; 2113 break; 2114 } 2115 default: 2116 break; 2117 } 2118 return false; 2119 } 2120 serverbrowser(guient * g)2121 int serverbrowser(guient *g) 2122 { 2123 if(servers.empty()) 2124 { 2125 g->pushlist(); 2126 g->text("No servers, press UPDATE to see some..", 0xFFFFFF); 2127 g->poplist(); 2128 return -1; 2129 } 2130 loopv(servers) if(!strcmp(servers[i]->sdesc, servermaster)) 2131 { 2132 if(servers[i]->attr[0] > GAMEVERSION) 2133 { 2134 g->pushlist(); 2135 g->textf("\fs\fgNEW VERSION RELEASED!\fS Please visit \fs\fb%s\fS for more information.", 0xFFFFFF, "info", ENG_URL); 2136 g->poplist(); 2137 } 2138 break; 2139 } 2140 int n = -1; 2141 for(int start = 0; start < servers.length();) 2142 { 2143 if(start > 0) g->tab(); 2144 int end = servers.length(); 2145 g->pushlist(); 2146 loopi(SINFO_MAX) 2147 { 2148 serverstartcolumn(g, i); 2149 for(int j = start; j < end; j++) 2150 { 2151 if(!i && g->shouldtab()) { end = j; break; } 2152 serverinfo *si = servers[j]; 2153 if(si->ping < 999 && si->attr.length() && si->attr[0] == GAMEVERSION) 2154 { 2155 if(serverentry(g, i, si)) n = j; 2156 } 2157 } 2158 serverendcolumn(g, i); 2159 } 2160 g->poplist(); 2161 start = end; 2162 } 2163 return n; 2164 } 2165 } 2166