diff --git a/config/admin.cfg b/config/admin.cfg index 5ef7b0fd..bf24cf05 100644 --- a/config/admin.cfg +++ b/config/admin.cfg @@ -11,4 +11,5 @@ alias stopdemo [ callvote 9 ] alias cleardemo [ callvote 10 $arg1 ] alias cleardemos [ callvote 10 0 ] alias serverdesc [ callvote 11 $arg1 ] +alias shuffleteams [ callvote 12 ] alias demo [ mode -3; map $arg1 ] diff --git a/source/src/client.cpp b/source/src/client.cpp index 99f08dcc..0aeb20fb 100644 --- a/source/src/client.cpp +++ b/source/src/client.cpp @@ -24,7 +24,7 @@ bool allowedittoggle() { bool allow = !curpeer || gamemode==1; if(!allow) conoutf("editing in multiplayer requires coopedit mode (1)"); - return allow; + return allow; } void setrate(int rate) @@ -62,7 +62,7 @@ void abortconnect() } void connects(char *servername, char *serverport, char *password) -{ +{ if(connpeer) { conoutf("aborting connection attempt"); @@ -97,13 +97,13 @@ void connects(char *servername, char *serverport, char *password) if(clienthost) { - connpeer = enet_host_connect(clienthost, &address, 3); + connpeer = enet_host_connect(clienthost, &address, 3); enet_host_flush(clienthost); connmillis = lastmillis; connattempts = 0; if(!m_mp(gamemode)) gamemode = GMODE_TEAMDEATHMATCH; } - else + else { conoutf("\f3could not connect to server"); clientpassword[0] = '\0'; @@ -120,7 +120,7 @@ void connectadmin(char *servername, char *serverport, char *password) void lanconnect() { connects(0); -} +} void disconnect(int onlyclean, int async) { @@ -182,8 +182,8 @@ void trydisconnect() disconnect(0, !discmillis); } -void toserver(char *text) -{ +void toserver(char *text) +{ bool toteam = text && text[0] == '%' && m_teammode; if(!toteam && text[0] == '%' && strlen(text) > 1) text++; // convert team-text to normal-text if no team-mode is active if(toteam) text++; @@ -267,7 +267,7 @@ void c2sinfo(playerent *d) // send update to the server { if(d->clientnum<0) return; // we haven't had a welcome message from the server yet if(lastmillis-lastupdate<40) return; // don't update faster than 25fps - + if(d->state==CS_ALIVE || d->state==CS_EDITING) { ENetPacket *packet = enet_packet_create(NULL, 100, 0); @@ -295,7 +295,7 @@ void c2sinfo(playerent *d) // send update to the server { ENetPacket *packet = enet_packet_create (NULL, MAXTRANS, 0); ucharbuf p(packet->data, packet->dataLength); - + if(!c2sinit) // tell other clients who I am { packet->flags = ENET_PACKET_FLAG_RELIABLE; @@ -311,6 +311,9 @@ void c2sinfo(playerent *d) // send update to the server putint(p, SV_ITEMLIST); if(!m_noitems) putitems(p); putint(p, -1); + putint(p, SV_SPAWNLIST); + putint(p, maploaded); + if(maploaded > 0) loopi(3) putint(p, numspawn[i]); senditemstoserver = false; } int i = 0; @@ -383,7 +386,7 @@ void gets2c() // get updates from the server if(editmode) toggleedit(); sendintro(); break; - + case ENET_EVENT_TYPE_RECEIVE: if(discmillis) conoutf("attempting to disconnect..."); else localservertoclient(event.channelID, event.packet->data, (int)event.packet->dataLength); @@ -434,19 +437,19 @@ void sendmap(char *mapname) { save_world(mapname); changemap(mapname); // FIXME!! - } + } else mapname = getclientmap(); if(securemapcheck(mapname)) return; - + int mapsize, cfgsize; - uchar *mapdata = readmap(path(mapname), &mapsize); + uchar *mapdata = readmap(path(mapname), &mapsize); uchar *cfgdata = readmcfg(path(mapname), &cfgsize); if(!mapdata) return; if(!cfgdata) cfgsize = 0; ENetPacket *packet = enet_packet_create(NULL, MAXTRANS + mapsize + cfgsize, ENET_PACKET_FLAG_RELIABLE); ucharbuf p(packet->data, packet->dataLength); - + putint(p, SV_SENDMAP); sendstring(mapname, p); putint(p, mapsize); @@ -459,14 +462,14 @@ void sendmap(char *mapname) enet_packet_destroy(packet); return; } - p.put(mapdata, mapsize); - delete[] mapdata; - if(cfgsize) + p.put(mapdata, mapsize); + delete[] mapdata; + if(cfgsize) { p.put(cfgdata, cfgsize); delete[] cfgdata; } - + enet_packet_resize(packet, p.length()); sendpackettoserv(2, packet); conoutf("sending map %s to server...", mapname); diff --git a/source/src/clientgame.cpp b/source/src/clientgame.cpp index 24f38186..f7b149fc 100644 --- a/source/src/clientgame.cpp +++ b/source/src/clientgame.cpp @@ -47,6 +47,7 @@ bool duplicatename(playerent *d, char *name = NULL) { if(!name) name = d->name; if(d!=player1 && !strcmp(name, player1->name)) return true; + if(!strcmp(name, "you")) return true; loopv(players) if(players[i] && d!=players[i] && !strcmp(name, players[i]->name)) return true; return false; } @@ -333,13 +334,17 @@ float nearestenemy(vec place, char *team) else return nearestenemydist; } -void findplayerstart(playerent *d, bool mapcenter) +void findplayerstart(playerent *d, bool mapcenter, int arenaspawn) { int r = fixspawn-->0 ? 4 : rnd(10)+1; entity *e = NULL; if(!mapcenter) { - if(m_teammode || m_arena) + if(m_arena && arenaspawn > -1) + { + ; // unfinished + } + else if(m_teammode || m_arena) { int type = m_teammode ? team_int(d->team) : 100; loopi(r) spawncycle = findentity(PLAYERSTART, spawncycle+1, type); @@ -600,9 +605,9 @@ void startmap(const char *name) // called just after a map load intermission = false; minutesremaining = -1; if(*clientmap) conoutf("game mode is \"%s\"", modestr(gamemode, modeacronyms > 0)); - + // run once - if(firstrun) + if(firstrun) { execfile("config/firstrun.cfg"); firstrun = false; @@ -693,7 +698,7 @@ COMMAND(dropflag, ARG_NONE); char *votestring(int type, char *arg1, char *arg2) { - const char *msgs[] = { "kick player %s", "ban player %s", "remove all bans", "set mastermode to %s", "%s autoteam", "force player %s to the enemy team", "give admin to player %s", "load map %s in mode %s", "%s demo recording for the next match", "stop demo recording", "clear all demos", "set server description to '%s'"}; + const char *msgs[] = { "kick player %s", "ban player %s", "remove all bans", "set mastermode to %s", "%s autoteam", "force player %s to the enemy team", "give admin to player %s", "load map %s in mode %s", "%s demo recording for the next match", "stop demo recording", "clear all demos", "set server description to '%s'", "shuffle teams"}; const char *msg = msgs[type]; char *out = newstring(_MAXDEFSTR); out[_MAXDEFSTR] = '\0'; @@ -767,6 +772,7 @@ void callvote(int type, char *arg1, char *arg2) break; case SA_STOPDEMO: case SA_REMBANS: + case SA_SHUFFLETEAMS: break; default: putint(p, atoi(arg1)); diff --git a/source/src/clients2c.cpp b/source/src/clients2c.cpp index bc4ef3f8..8f6716d4 100644 --- a/source/src/clients2c.cpp +++ b/source/src/clients2c.cpp @@ -233,6 +233,12 @@ void parsemessages(int cn, playerent *d, ucharbuf &p) break; } + case SV_SPAWNLIST: + { + if(getint(p) > 0) loopi(3) getint(p); + break; + } + case SV_MAPRELOAD: // server requests next map { getint(p); @@ -312,10 +318,11 @@ void parsemessages(int cn, playerent *d, ucharbuf &p) player1->armour = getint(p); player1->setprimary(getint(p)); player1->selectweapon(getint(p)); + int arenaspawn = getint(p); loopi(NUMGUNS) player1->ammo[i] = getint(p); loopi(NUMGUNS) player1->mag[i] = getint(p); player1->state = CS_ALIVE; - findplayerstart(player1); + findplayerstart(player1, false, arenaspawn); if(player1->skin!=player1->nextskin) setskin(player1, player1->nextskin); if(m_arena) { @@ -606,18 +613,39 @@ void parsemessages(int cn, playerent *d, ucharbuf &p) break; } - case SV_FORCETEAM: - { + case SV_FORCETEAM: + { int team = getint(p); bool respawn = getint(p) == 1; - changeteam(team, respawn); - break; - } + changeteam(team, respawn); + break; + } + + case SV_FORCENOTIFY: + { + int fpl = getint(p); + int fnt = getint(p); + playerent *d = getclient(fpl); + bool you = fpl == player1->clientnum; // sound? + bool et = team_int(player1->team) != fnt; + conoutf("the server forced %s to %s team", you ? "you" : d ? colorname(d) : "", et ? "the enemy" : "your"); + break; + } case SV_AUTOTEAM: - autoteambalance = 1 == getint(p); + { + int na = getint(p); + switch(na) + { + case AT_ENABLED: + case AT_DISABLED: + autoteambalance = na == AT_ENABLED; + break; + case AT_SHUFFLE: // sound? + break; + } break; - + } case SV_CALLVOTE: { int type = getint(p); diff --git a/source/src/cube.h b/source/src/cube.h index 2daa4082..997ffdd4 100644 --- a/source/src/cube.h +++ b/source/src/cube.h @@ -44,6 +44,7 @@ extern int maxclients; extern hashtable mapinfo; extern bool hasTE, hasMT, hasMDA; extern int hwtexsize; +extern int numspawn[3], maploaded; #include "protos.h" // external function decls diff --git a/source/src/entity.h b/source/src/entity.h index 477af949..8e87b6dd 100644 --- a/source/src/entity.h +++ b/source/src/entity.h @@ -2,7 +2,7 @@ enum // static entity types { NOTUSED = 0, // entity slot not in use in map LIGHT, // lightsource, attr1 = radius, attr2 = intensity - PLAYERSTART, // attr1 = angle + PLAYERSTART, // attr1 = angle, attr2 = team I_CLIPS, I_AMMO,I_GRENADE, I_HEALTH, I_ARMOUR, I_AKIMBO, MAPMODEL, // attr1 = angle, attr2 = idx diff --git a/source/src/protocol.cpp b/source/src/protocol.cpp index 96c0e9ed..94c0c2a8 100644 --- a/source/src/protocol.cpp +++ b/source/src/protocol.cpp @@ -36,7 +36,7 @@ void putuint(ucharbuf &p, int n) p.put(0x80 | (n & 0x7F)); p.put(n >> 7); } - else + else { p.put(0x80 | (n & 0x7F)); p.put(0x80 | ((n >> 7) & 0x7F)); @@ -96,7 +96,7 @@ void filtertext(char *dst, const char *src, bool whitespace, int len) const char *modefullnames[] = { "team deathmatch", "coopedit", "deathmatch", "survivor", - "team survivor", "ctf", "pistol frenzy", "bot team deathmatch", "bot deathmatch", "last swiss standing", + "team survivor", "ctf", "pistol frenzy", "bot team deathmatch", "bot deathmatch", "last swiss standing", "one shot, one kill", "team one shot, one kill", "bot one shot, one kill" }; @@ -114,11 +114,11 @@ const char *modestr(int n, bool acronyms) { return acronyms ? acronymmodestr (n) const char *voteerrorstr(int n) { return (n>=0 && (size_t)n < sizeof(voteerrors)/sizeof(voteerrors[0])) ? voteerrors[n] : "unknown"; } char msgsizesl[] = // size inclusive message token, 0 for variable or not-checked sizes -{ +{ SV_INITS2C, 5, SV_INITC2S, 0, SV_POS, 0, SV_TEXT, 0, SV_TEAMTEXT, 0, SV_SOUND, 2, SV_VOICECOM, 2, SV_VOICECOMTEAM, 2, SV_CDIS, 2, SV_SHOOT, 0, SV_EXPLODE, 0, SV_SUICIDE, 1, SV_AKIMBO, 2, SV_RELOAD, 3, SV_GIBDIED, 4, SV_DIED, 4, SV_GIBDAMAGE, 6, SV_DAMAGE, 6, SV_HITPUSH, 6, SV_SHOTFX, 9, SV_THROWNADE, 8, - SV_TRYSPAWN, 1, SV_SPAWNSTATE, 22, SV_SPAWN, 3, SV_FORCEDEATH, 2, SV_RESUME, 0, + SV_TRYSPAWN, 1, SV_SPAWNSTATE, 23, SV_SPAWN, 3, SV_FORCEDEATH, 2, SV_RESUME, 0, SV_TIMEUP, 2, SV_EDITENT, 10, SV_MAPRELOAD, 2, SV_NEXTMAP, 0, SV_ITEMACC, 2, SV_MAPCHANGE, 0, SV_ITEMSPAWN, 2, SV_ITEMPICKUP, 2, SV_PING, 2, SV_PONG, 2, SV_CLIENTPING, 2, SV_GAMEMODE, 2, @@ -127,14 +127,15 @@ char msgsizesl[] = // size inclusive message token, 0 for variable SV_MODELSKIN, 2, SV_FLAGPICKUP, 2, SV_FLAGDROP, 2, SV_FLAGRETURN, 2, SV_FLAGSCORE, 2, SV_FLAGRESET, 2, SV_FLAGINFO, 0, SV_FLAGS, 2, SV_ARENAWIN, 2, - SV_SETADMIN, 0, SV_SERVOPINFO, 3, + SV_SETADMIN, 0, SV_SERVOPINFO, 3, SV_CALLVOTE, 0, SV_CALLVOTESUC, 1, SV_CALLVOTEERR, 2, SV_VOTE, 2, SV_VOTERESULT, 2, - SV_FORCETEAM, 2, SV_AUTOTEAM, 2, SV_CHANGETEAM, 1, + SV_FORCETEAM, 2, SV_AUTOTEAM, 2, SV_CHANGETEAM, 1, SV_WHOIS, 2, SV_WHOISINFO, 3, - SV_LISTDEMOS, 1, SV_SENDDEMOLIST, 0, SV_GETDEMO, 2, SV_SENDDEMO, 0, SV_DEMOPLAYBACK, 2, - SV_CONNECT, 0, + SV_LISTDEMOS, 1, SV_SENDDEMOLIST, 0, SV_GETDEMO, 2, SV_SENDDEMO, 0, SV_DEMOPLAYBACK, 2, + SV_CONNECT, 0, SV_CLIENT, 0, SV_EXTENSION, 0, + SV_SPAWNLIST, 0, SV_FORCENOTIFY, 3, -1 }; diff --git a/source/src/protocol.h b/source/src/protocol.h index d9081f2d..3d5a229e 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -31,13 +31,15 @@ enum SV_LISTDEMOS, SV_SENDDEMOLIST, SV_GETDEMO, SV_SENDDEMO, SV_DEMOPLAYBACK, SV_CONNECT, SV_CLIENT, - SV_EXTENSION + SV_EXTENSION, + SV_SPAWNLIST, SV_FORCENOTIFY }; -enum { SA_KICK = 0, SA_BAN, SA_REMBANS, SA_MASTERMODE, SA_AUTOTEAM, SA_FORCETEAM, SA_GIVEADMIN, SA_MAP, SA_RECORDDEMO, SA_STOPDEMO, SA_CLEARDEMOS, SA_SERVERDESC, SA_NUM}; +enum { SA_KICK = 0, SA_BAN, SA_REMBANS, SA_MASTERMODE, SA_AUTOTEAM, SA_FORCETEAM, SA_GIVEADMIN, SA_MAP, SA_RECORDDEMO, SA_STOPDEMO, SA_CLEARDEMOS, SA_SERVERDESC, SA_SHUFFLETEAMS, SA_NUM}; enum { VOTE_NEUTRAL = 0, VOTE_YES, VOTE_NO, VOTE_NUM }; enum { VOTEE_DISABLED = 0, VOTEE_CUR, VOTEE_MUL, VOTEE_MAX, VOTEE_AREA, VOTEE_PERMISSION, VOTEE_INVALID, VOTEE_NUM }; enum { MM_OPEN, MM_PRIVATE, MM_NUM }; +enum { AT_DISABLED = 0, AT_ENABLED, AT_SHUFFLE, AT_NUM }; #define DMF 16.0f #define DNF 100.0f diff --git a/source/src/protos.h b/source/src/protos.h index 178dd404..065b9c99 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -312,7 +312,7 @@ extern void flaginbase(int flag, int action, int act); extern void flagmsg(int flag, int action); extern void arenarespawn(); extern bool tryrespawn(); -extern void findplayerstart(playerent *d, bool mapcenter=false); +extern void findplayerstart(playerent *d, bool mapcenter=false, int arenaspawn=-1); extern void serveropcommand(int cmd, int arg1); extern void refreshsopmenu(void *menu, bool init); extern char *colorname(playerent *d, int num = 0, char *name = NULL, const char *prefix = ""); diff --git a/source/src/server.cpp b/source/src/server.cpp index 50fad4b3..67893cad 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -15,6 +15,7 @@ void resetmap(const char *newname, int newmode, int newtime = -1, bool notify = true); void disconnect_client(int n, int reason = -1); +bool refillteams(bool now = false, bool notify = true); servercontroller *svcctrl = NULL; struct log *logger = NULL; @@ -217,6 +218,8 @@ struct client // server side version of "dynent" type vector position, messages; string lastsaytext; int saychars, lastsay, spamcount; + int at3_score, at3_lastforce; + bool at3_dontmove; gameevent &addevent() { @@ -232,6 +235,7 @@ struct client // server side version of "dynent" type events.setsizenodelete(0); timesync = false; lastevent = 0; + at3_lastforce = 0; } void reset() @@ -288,7 +292,7 @@ void cleanworldstate(ENetPacket *packet) } } -int bsend = 0, brec = 0, laststatus = 0, servmillis = 0; +int bsend = 0, brec = 0, laststatus = 0, servmillis = 0, lastfillup = 0; void recordpacket(int chan, void *data, int len); @@ -454,6 +458,7 @@ struct server_entity // server side version of "entity" type vector sents; bool notgotitems = true; // true when map has changed and waiting for clients to send item +int clnumspawn[3]; // allows the gamemode macros to work with the server mode #define gamemode smode @@ -542,9 +547,9 @@ void sendspawn(client *c) { clientstate &gs = c->state; spawnstate(c); - sendf(c->clientnum, 1, "ri6vv", SV_SPAWNSTATE, gs.lifesequence, + sendf(c->clientnum, 1, "ri7vv", SV_SPAWNSTATE, gs.lifesequence, gs.health, gs.armour, - gs.primary, gs.gunselect, + gs.primary, gs.gunselect, -1, // unfinished NUMGUNS, gs.ammo, NUMGUNS, gs.mag); gs.lastspawn = gamemillis; } @@ -954,6 +959,7 @@ void arenacheck() if(!dead) return; sendf(-1, 1, "ri2", SV_ARENAWIN, !alive ? -1 : alive->clientnum); arenaround = gamemillis+5000; + if (autoteam && m_teammode) refillteams(true); } #define SPAMREPEATINTERVAL 15 // detect doubled lines only if interval < 15 seconds @@ -1275,23 +1281,128 @@ void resetvotes() loopv(clients) clients[i]->vote = VOTE_NEUTRAL; } -void forceteam(int client, int team, bool respawn) +void forceteam(int client, int team, bool respawn, bool notify = false) { if(!valid_client(client) || team < 0 || team > 1) return; sendf(client, 1, "riii", SV_FORCETEAM, team, respawn ? 1 : 0); + if(notify) sendf(-1, 1, "riii", SV_FORCENOTIFY, client, team); } +void calcscores() +{ + loopv(clients) if (clients[i]->type!=ST_EMPTY) + { + clients[i]->at3_score = clients[i]->state.frags * 100 / (clients[i]->state.deaths ? clients[i]->state.deaths : 1) + + clients[i]->state.flagscore * (33 + 33 * (clients[i]->state.flagscore < 3)); + } +} + +int cmpsc(const void *a, const void * b) { return clients[*((int *)a)]->at3_score - clients[*((int *)b)]->at3_score; } + void shuffleteams(bool respawn = true) { - int numplayers = numclients(); - int teamsize[2] = {0, 0}; - loopv(clients) if(clients[i]->type!=ST_EMPTY) - { - int team = rnd(2); - if(teamsize[team] >= numplayers/2) team = team_opposite(team); - forceteam(i, team, respawn); - teamsize[team]++; - } + int numplayers = numclients(); + if(gamemillis < 2 * 60 *1000) + { // random + int teamsize[2] = {0, 0}; + loopv(clients) if(clients[i]->type!=ST_EMPTY) + { + int team = rnd(2); + if(teamsize[team] >= numplayers/2) team = team_opposite(team); + forceteam(i, team, respawn); + teamsize[team]++; + } + } + else + { // skill sorted + calcscores(); + int *list = new int[numplayers], t = rnd(2), j = 0; + loopv(clients) if(clients[i]->type!=ST_EMPTY) list[j++] = i; + qsort(list, numplayers, sizeof(int), cmpsc); + loopi(numplayers) + { + forceteam(list[i], t, respawn); + t = !t; + } + } +} + +bool refillteams(bool now, bool notify) // force only minimal amounts of players +{ + static int lasttime_eventeams = 0; + int teamsize[2] = {0, 0}, teamscore[2] = {0, 0}, moveable[2] = {0, 0}; + bool switched = false; + + calcscores(); + loopv(clients) if (clients[i]->type!=ST_EMPTY) // playerlist stocktaking + { + clients[i]->at3_dontmove = true; + if(!clients[i]->awaitdisc) + { + int t = 0; + if(!strcmp(clients[i]->team, "CLA") || t++ || !strcmp(clients[i]->team, "RVSF")) // need exact teams here + { + teamsize[t]++; + teamscore[t] += clients[i]->at3_score; + if(!m_ctf || !((sflaginfos[0].state==CTFF_STOLEN && sflaginfos[0].actor_cn==i) || + (sflaginfos[1].state==CTFF_STOLEN && sflaginfos[1].actor_cn==i) )) + { + clients[i]->at3_dontmove = false; + moveable[t]++; + } + } + } + } + int bigteam = teamsize[1] > teamsize[0]; + int allplayers = teamsize[0] + teamsize[1]; + int diffnum = teamsize[bigteam] - teamsize[!bigteam]; + int diffscore = teamscore[bigteam] - teamscore[!bigteam]; + if(lasttime_eventeams > gamemillis) lasttime_eventeams = 0; + if(diffnum > 1) + { + if(now || gamemillis - lasttime_eventeams > 8000 + allplayers * 1000 || diffnum > 2 + allplayers / 10) + { + // time to even out teams + loopv(clients) if (clients[i]->type!=ST_EMPTY && team_int(clients[i]->team) != bigteam) clients[i]->at3_dontmove = true; // dont move small team players + while(diffnum > 1 && moveable[bigteam] > 0) + { + // pick best fitting cn + string atlog, buf; // debug logging - will be removed + int pick = -1; + int bestfit = 1e9; + int targetscore = diffscore / (diffnum & ~1); + s_sprintf(atlog)("at-target: %d, ", targetscore); + loopv(clients) if (clients[i]->type!=ST_EMPTY && !clients[i]->at3_dontmove) // try all still movable players + { + int fit = targetscore - clients[i]->at3_score; + if(fit < 0 ) fit = -(fit * 15) / 10; // avoid too good players + int forcedelay = clients[i]->at3_lastforce ? 1000 - (gamemillis - clients[i]->at3_lastforce) / 5 * 60 : 0; + if(forcedelay > 0) fit += (fit * forcedelay) / 600; // avoid lately forced players + if(fit < bestfit + fit * rnd(100) / 400) // search 'almost' best fit + { + bestfit = fit; + pick = i; + } + s_sprintf(buf)("%d:%d ", i, fit); s_strcat(atlog, buf); + } + if(pick < 0) break; // should really never happen + // move picked player + forceteam(pick, !bigteam, false); + + diffnum -= 2; + diffscore -= 2 * clients[pick]->at3_score; + moveable[bigteam]--; + clients[pick]->at3_dontmove = true; + clients[pick]->at3_lastforce = gamemillis; // try not to force this player again for the next 5 minutes + switched = true; + s_sprintf(buf)(" pick:%d", pick); s_strcat(atlog, buf); + logger->writeline(log::info,"%s", atlog); + } + } + } + if(diffnum < 2) + lasttime_eventeams = gamemillis; + return switched; } bool mapavailable(const char *mapname); @@ -1316,8 +1427,10 @@ void resetmap(const char *newname, int newmode, int newtime, bool notify) mapreload = false; interm = 0; laststatus = servmillis-61*1000; + lastfillup = servmillis; resetvotes(); resetitems(); + loopi(3) clnumspawn[i] = 0; scores.setsize(0); ctfreset(); if(notify) @@ -1330,7 +1443,13 @@ void resetmap(const char *newname, int newmode, int newtime, bool notify) if(notify) { // shuffle if previous mode wasn't a team-mode - if(m_teammode && !lastteammode) shuffleteams(false); + if(m_teammode) + { + if(!lastteammode) + shuffleteams(false); + else if (autoteam) + refillteams(true, false); + } // send spawns loopv(clients) if(clients[i]->type!=ST_EMPTY) { @@ -1604,7 +1723,7 @@ bool sendmapserv(int n, string mapname, int mapsize, int cfgsize, uchar *data) ENetPacket *getmapserv(int n) { - if(!copydata) return NULL; + if(!copydata || !mapavailable(smapname)) return NULL; ENetPacket *packet = enet_packet_create(NULL, MAXTRANS + copysize, ENET_PACKET_FLAG_RELIABLE); ucharbuf p(packet->data, packet->dataLength); putint(p, SV_RECVMAP); @@ -1624,6 +1743,7 @@ void getservermap(void) int cgzsize, cfgsize; const char *name = behindpath(smapname); // no paths allowed here + if(!strcmp(name, behindpath(copyname))) return; s_sprintf(cgzname)(SERVERMAP_PATH "%s.cgz", name); path(cgzname); if(fileexists(cgzname, "r")) @@ -1714,6 +1834,7 @@ void welcomepacket(ucharbuf &p, int n) putint(p, gs.armour); putint(p, gs.primary); putint(p, gs.gunselect); + putint(p, -1); // unfinished loopi(NUMGUNS) putint(p, gs.ammo[i]); loopi(NUMGUNS) putint(p, gs.mag[i]); gs.lastspawn = gamemillis; @@ -1926,6 +2047,13 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 break; } + case SV_SPAWNLIST: + { + if(getint(p) > 0) loopi(3) clnumspawn[i] = getint(p); + QUEUE_MSG; + break; + } + case SV_ITEMPICKUP: { int n = getint(p); @@ -2173,6 +2301,9 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 case SA_AUTOTEAM: vi->action = new autoteamaction(getint(p) > 0); break; + case SA_SHUFFLETEAMS: + vi->action = new shuffleteamaction(); + break; case SA_FORCETEAM: vi->action = new forceteamaction(getint(p)); break; @@ -2383,6 +2514,8 @@ void serverslice(uint timeout) // main server update, called from cube main lo serverms(smode, numclients(), minremain, smapname, servmillis, serverhost->address.port); + if(autoteam && m_teammode && !m_arena && !interm && servmillis - lastfillup > 1000 && refillteams()) lastfillup = servmillis; + if(servmillis-laststatus>60*1000) // display bandwidth stats, useful for server ops { laststatus = servmillis; diff --git a/source/src/serveractions.h b/source/src/serveractions.h index 6b9c86cf..3853fed8 100644 --- a/source/src/serveractions.h +++ b/source/src/serveractions.h @@ -123,8 +123,8 @@ struct autoteamaction : enableaction { void perform() { - sendf(-1, 1, "ri2", SV_AUTOTEAM, (autoteam = enable) == 1 ? 1 : 0); - if(m_teammode && enable) shuffleteams(); + sendf(-1, 1, "ri2", SV_AUTOTEAM, (autoteam = enable) == 1 ? AT_ENABLED : AT_DISABLED); + if(m_teammode && enable) refillteams(true); } autoteamaction(bool enable) : enableaction(enable) { @@ -132,6 +132,20 @@ struct autoteamaction : enableaction } }; +struct shuffleteamaction : serveraction +{ + void perform() + { + sendf(-1, 1, "ri2", SV_AUTOTEAM, AT_SHUFFLE); + shuffleteams(); + } + bool isvalid() { return serveraction::isvalid() && m_teammode; } + shuffleteamaction() + { + if(isvalid()) s_strcpy(desc, "shuffle teams"); + } +}; + struct recorddemoaction : enableaction { void perform() { demonextmatch = enable; } diff --git a/source/src/tools.cpp b/source/src/tools.cpp index 52118131..0374729e 100644 --- a/source/src/tools.cpp +++ b/source/src/tools.cpp @@ -144,6 +144,18 @@ const char *findfile(const char *filename, const char *mode) return filename; } +int getfilesize(const char *filename) +{ + const char *found = findfile(filename, "rb"); + if(!found) return -1; + FILE *fp = fopen(found, "rb"); + if(!fp) return -1; + fseek(fp, 0, SEEK_END); + int len = ftell(fp); + fclose(fp); + return len; +} + FILE *openfile(const char *filename, const char *mode) { const char *found = findfile(filename, mode); diff --git a/source/src/tools.h b/source/src/tools.h index 156c0006..6b0527ae 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -501,6 +501,7 @@ extern bool createdir(const char *path); extern void sethomedir(const char *dir); extern void addpackagedir(const char *dir); extern const char *findfile(const char *filename, const char *mode); +extern int getfilesize(const char *filename); extern FILE *openfile(const char *filename, const char *mode); extern gzFile opengzfile(const char *filename, const char *mode); extern char *loadfile(const char *fn, int *size, const char *mode = NULL); diff --git a/source/src/worldio.cpp b/source/src/worldio.cpp index 8a341416..bace283c 100644 --- a/source/src/worldio.cpp +++ b/source/src/worldio.cpp @@ -229,6 +229,7 @@ void save_world(char *mname) } extern void preparectf(bool cleanonly = false); +int numspawn[3], maploaded = 0; bool load_world(char *mname) // still supports all map formats that have existed since the earliest cube betas! { @@ -237,6 +238,7 @@ bool load_world(char *mname) // still supports all map formats that have pruneundos(); clearworldsounds(); setnames(mname); + maploaded = getfilesize(cgzname); gzFile f = opengzfile(cgzname, "rb9"); if(!f) { conoutf("\f3could not read map %s", cgzname); return false; } loadingscreen(); @@ -256,6 +258,7 @@ bool load_world(char *mname) // still supports all map formats that have hdr.waterlevel = -100000; } ents.setsize(0); + loopi(3) numspawn[i] = 0; loopi(hdr.numents) { entity &e = ents.add(); @@ -304,6 +307,13 @@ bool load_world(char *mname) // still supports all map formats that have e.type=NOTUSED; } } + if(e.type == PLAYERSTART && (e.attr2 == 0 || e.attr2 == 1 || e.attr2 == 100)) + { + if(e.attr2 == 100) + numspawn[2]++; + else + numspawn[e.attr2]++; + } } delete[] world; setupworld(hdr.sfactor);