summaryrefslogtreecommitdiff
path: root/src/fpsgame/server.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/fpsgame/server.cpp')
-rw-r--r--src/fpsgame/server.cpp338
1 files changed, 274 insertions, 64 deletions
diff --git a/src/fpsgame/server.cpp b/src/fpsgame/server.cpp
index 341b050..8f17ee2 100644
--- a/src/fpsgame/server.cpp
+++ b/src/fpsgame/server.cpp
@@ -263,7 +263,6 @@ namespace server {
extern void reqadd(clientinfo *ci, int skill);
extern void reqdel(clientinfo *ci);
extern void setbotlimit(clientinfo *ci, int limit);
- extern void setbotbalance(clientinfo *ci, bool balance);
extern void changemap();
extern void addclient(clientinfo *ci);
extern void changeteam(clientinfo *ci);
@@ -1894,63 +1893,12 @@ namespace server {
crcinfo(int crc, int matches) : crc(crc), matches(matches) {}
static bool compare(const crcinfo &x, const crcinfo &y) { return x.matches > y.matches; }
};
- VAR(modifiedmapspectator, 0, 1, 2);
- void checkmaps(int req = -1) {
- if(m_edit || !smapname[0]) return;
- vector<crcinfo> crcs;
- int total = 0, unsent = 0, invalid = 0;
- if(mcrc) crcs.add(crcinfo(mcrc, clients.length() + 1));
- loopv(clients) {
- clientinfo *ci = clients[i];
- if(ci->state.state==CS_SPECTATOR || ci->state.aitype != AI_NONE) continue;
- total++;
- if(!ci->clientmap[0]) {
- if(ci->mapcrc < 0) invalid++;
- else if(!ci->mapcrc) unsent++;
- }
- else {
- crcinfo *match = NULL;
- loopvj(crcs) if(crcs[j].crc == ci->mapcrc) { match = &crcs[j]; break; }
- if(!match) crcs.add(crcinfo(ci->mapcrc, 1));
- else match->matches++;
- }
- }
- if(!mcrc && total - unsent < min(total, 4)) return;
- crcs.sort(crcinfo::compare);
- string msg;
- loopv(clients) {
- clientinfo *ci = clients[i];
- if(ci->state.state==CS_SPECTATOR || ci->state.aitype != AI_NONE || ci->clientmap[0] || ci->mapcrc >= 0 || (req < 0 && ci->warned)) continue;
- formatstring(msg, "%s has modified map \"%s\"", colorname(ci), smapname);
- sendf(req, 1, "ris", N_SERVMSG, msg);
- if(req < 0) ci->warned = true;
- }
- if(crcs.length() >= 2) loopv(crcs) {
- crcinfo &info = crcs[i];
- if(i || info.matches <= crcs[i+1].matches) loopvj(clients) {
- clientinfo *ci = clients[j];
- if(ci->state.state==CS_SPECTATOR || ci->state.aitype != AI_NONE || !ci->clientmap[0] || ci->mapcrc != info.crc || (req < 0 && ci->warned)) continue;
- formatstring(msg, "%s has modified map \"%s\"", colorname(ci), smapname);
- sendf(req, 1, "ris", N_SERVMSG, msg);
- if(req < 0) ci->warned = true;
- }
- }
- if(req < 0 && modifiedmapspectator && (mcrc || modifiedmapspectator > 1)) loopv(clients) {
- clientinfo *ci = clients[i];
- if(!ci->local && ci->warned && ci->state.state != CS_SPECTATOR) forcespectator(ci);
- }
- }
- bool shouldspectate(clientinfo *ci) {
- return !ci->local && ci->warned && modifiedmapspectator && (mcrc || modifiedmapspectator > 1);
- }
void unspectate(clientinfo *ci) {
- if(shouldspectate(ci)) return;
ci->state.state = CS_DEAD;
ci->state.respawn();
ci->state.lasttimeplayed = lastmillis;
aiman::addclient(ci);
sendf(-1, 1, "ri3", N_SPECTATOR, ci->clientnum, 0);
- if(ci->clientmap[0] || ci->mapcrc) checkmaps();
if(!hasmap(ci)) rotatemap(true);
}
void sendservinfo(clientinfo *ci) {
@@ -2350,18 +2298,13 @@ namespace server {
}
copystring(ci->clientmap, text);
ci->mapcrc = text[0] ? crc : 1;
- checkmaps();
if(cq && cq != ci && cq->ownernum != ci->clientnum) cq = NULL;
break;
}
- case N_CHECKMAPS:
- checkmaps(sender);
- break;
case N_TRYSPAWN:
if(!ci || !cq || cq->state.state!=CS_DEAD || cq->state.lastspawn>=0) break;
if(!ci->clientmap[0] && !ci->mapcrc) {
ci->mapcrc = -1;
- checkmaps();
if(ci == cq) { if(ci->state.state != CS_DEAD) break; }
else if(cq->ownernum != ci->clientnum) { cq = NULL; break; }
}
@@ -2700,11 +2643,6 @@ namespace server {
if(ci) aiman::setbotlimit(ci, limit);
break;
}
- case N_BOTBALANCE: {
- int balance = getint(p);
- if(ci) aiman::setbotbalance(ci, balance!=0);
- break;
- }
case N_AUTHTRY: {
string desc, name;
getstring(desc, p, sizeof(desc));
@@ -2819,7 +2757,102 @@ namespace server {
const char *defaultmaster() { return "master.sauerbraten.org"; }
int masterport() { return SAUERBRATEN_MASTER_PORT; }
int numchannels() { return 3; }
- #include "extinfo.h"
+
+#define EXT_ACK -1
+#define EXT_VERSION 105
+#define EXT_NO_ERROR 0
+#define EXT_ERROR 1
+#define EXT_PLAYERSTATS_RESP_IDS -10
+#define EXT_PLAYERSTATS_RESP_STATS -11
+#define EXT_UPTIME 0
+#define EXT_PLAYERSTATS 1
+#define EXT_TEAMSCORE 2
+
+ VAR(extinfoip, 0, 0, 1);
+ void extinfoplayer(ucharbuf &p, clientinfo *ci) {
+ ucharbuf q = p;
+ putint(q, EXT_PLAYERSTATS_RESP_STATS); // send player stats following
+ putint(q, ci->clientnum); //add player id
+ putint(q, ci->ping);
+ sendstring(ci->name, q);
+ sendstring(ci->team, q);
+ putint(q, ci->state.frags);
+ putint(q, ci->state.flags);
+ putint(q, ci->state.deaths);
+ putint(q, ci->state.teamkills);
+ putint(q, ci->state.damage*100/max(ci->state.shotdamage,1));
+ putint(q, ci->state.health);
+ putint(q, ci->state.armour);
+ putint(q, ci->state.gunselect);
+ putint(q, ci->privilege);
+ putint(q, ci->state.state);
+ uint ip = extinfoip ? getclientip(ci->clientnum) : 0;
+ q.put((uchar*)&ip, 3);
+ sendserverinforeply(q);
+ }
+ static inline void extinfoteamscore(ucharbuf &p, const char *team, int score) {
+ sendstring(team, p);
+ putint(p, score);
+ putint(p,-1); //no bases follow
+ }
+ void extinfoteams(ucharbuf &p) {
+ putint(p, m_teammode ? 0 : 1);
+ putint(p, gamemode);
+ putint(p, max((gamelimit - gamemillis)/1000, 0));
+ if(!m_teammode) return;
+ vector<teamscore> scores;
+ loopv(clients) {
+ clientinfo *ci = clients[i];
+ if(ci->state.state!=CS_SPECTATOR && ci->team[0] && scores.htfind(ci->team) < 0) {
+ teaminfo *ti = teaminfos.access(ci->team);
+ scores.add(teamscore(ci->team, ti ? ti->frags : 0));
+ }
+ }
+ loopv(scores) extinfoteamscore(p, scores[i].team, scores[i].score);
+ }
+ void extserverinforeply(ucharbuf &req, ucharbuf &p) {
+ int extcmd = getint(req); // extended commands
+ //Build a new packet
+ putint(p, EXT_ACK); //send ack
+ putint(p, EXT_VERSION); //send version of extended info
+ switch(extcmd) {
+ case EXT_UPTIME: {
+ putint(p, totalsecs); //in seconds
+ break;
+ }
+ case EXT_PLAYERSTATS: {
+ int cn = getint(req); //a special player, -1 for all
+ clientinfo *ci = NULL;
+ if(cn >= 0) {
+ loopv(clients) if(clients[i]->clientnum == cn) { ci = clients[i]; break; }
+ if(!ci) {
+ putint(p, EXT_ERROR); //client requested by id was not found
+ sendserverinforeply(p);
+ return;
+ }
+ }
+ putint(p, EXT_NO_ERROR); //so far no error can happen anymore
+ ucharbuf q = p; //remember buffer position
+ putint(q, EXT_PLAYERSTATS_RESP_IDS); //send player ids following
+ if(ci) putint(q, ci->clientnum);
+ else loopv(clients) putint(q, clients[i]->clientnum);
+ sendserverinforeply(q);
+ if(ci) extinfoplayer(p, ci);
+ else loopv(clients) extinfoplayer(p, clients[i]);
+ return;
+ }
+ case EXT_TEAMSCORE: {
+ extinfoteams(p);
+ break;
+ }
+ default: {
+ putint(p, EXT_ERROR);
+ break;
+ }
+ }
+ sendserverinforeply(p);
+ }
+
void serverinforeply(ucharbuf &req, ucharbuf &p) {
if(req.remaining() && !getint(req)) {
extserverinforeply(req, p);
@@ -2840,6 +2873,183 @@ namespace server {
sendstring(serverdesc, p);
sendserverinforeply(p);
}
- #include "aiman.h"
+
+ // server-side ai manager
+ namespace aiman {
+ bool dorefresh = false;
+ VARN(serverbotlimit, botlimit, 0, 8, MAXBOTS);
+ void calcteams(vector<teamscore> &teams) {
+ static const char * const defaults[2] = { "good", "evil" };
+ loopv(clients) {
+ clientinfo *ci = clients[i];
+ if(ci->state.state==CS_SPECTATOR || !ci->team[0]) continue;
+ teamscore *t = NULL;
+ loopvj(teams) if(!strcmp(teams[j].team, ci->team)) { t = &teams[j]; break; }
+ if(t) t->score++;
+ else teams.add(teamscore(ci->team, 1));
+ }
+ teams.sort(teamscore::compare);
+ if(teams.length() < int(sizeof(defaults)/sizeof(defaults[0]))) {
+ loopi(sizeof(defaults)/sizeof(defaults[0])) if(teams.htfind(defaults[i]) < 0) teams.add(teamscore(defaults[i], 0));
+ }
+ }
+
+ const char *chooseteam() {
+ vector<teamscore> teams;
+ calcteams(teams);
+ return teams.length() ? teams.last().team : "";
+ }
+ static inline bool validaiclient(clientinfo *ci) {
+ return ci->clientnum >= 0 && ci->state.aitype == AI_NONE && (ci->state.state!=CS_SPECTATOR || ci->local || (ci->privilege && !ci->warned));
+ }
+ clientinfo *findaiclient(clientinfo *exclude = NULL) {
+ clientinfo *least = NULL;
+ loopv(clients) {
+ clientinfo *ci = clients[i];
+ if(!validaiclient(ci) || ci==exclude) continue;
+ if(!least || ci->bots.length() < least->bots.length()) least = ci;
+ }
+ return least;
+ }
+ bool addai(int skill, int limit) {
+ int numai = 0, cn = -1, maxai = limit >= 0 ? min(limit, MAXBOTS) : MAXBOTS;
+ loopv(bots) {
+ clientinfo *ci = bots[i];
+ if(!ci || ci->ownernum < 0) { if(cn < 0) cn = i; continue; }
+ numai++;
+ }
+ if(numai >= maxai) return false;
+ if(bots.inrange(cn)) {
+ clientinfo *ci = bots[cn];
+ if(ci) {
+ // reuse a slot that was going to removed
+ clientinfo *owner = findaiclient();
+ ci->ownernum = owner ? owner->clientnum : -1;
+ if(owner) owner->bots.add(ci);
+ ci->aireinit = 2;
+ dorefresh = true;
+ return true;
+ }
+ }
+ else { cn = bots.length(); bots.add(NULL); }
+ const char *team = m_teammode ? chooseteam() : "";
+ if(!bots[cn]) bots[cn] = new clientinfo;
+ clientinfo *ci = bots[cn];
+ ci->clientnum = MAXCLIENTS + cn;
+ ci->state.aitype = AI_BOT;
+ clientinfo *owner = findaiclient();
+ ci->ownernum = owner ? owner->clientnum : -1;
+ if(owner) owner->bots.add(ci);
+ ci->state.skill = skill <= 0 ? rnd(50) + 51 : clamp(skill, 1, 101);
+ clients.add(ci);
+ ci->state.lasttimeplayed = lastmillis;
+ copystring(ci->name, "bot", MAXNAMELEN+1);
+ ci->state.state = CS_DEAD;
+ copystring(ci->team, team, MAXTEAMLEN+1);
+ ci->playermodel = 0;
+ ci->aireinit = 2;
+ ci->connected = true;
+ dorefresh = true;
+ return true;
+ }
+ void deleteai(clientinfo *ci) {
+ int cn = ci->clientnum - MAXCLIENTS;
+ if(!bots.inrange(cn)) return;
+ sendf(-1, 1, "ri2", N_CDIS, ci->clientnum);
+ clientinfo *owner = (clientinfo *)getclientinfo(ci->ownernum);
+ if(owner) owner->bots.removeobj(ci);
+ clients.removeobj(ci);
+ DELETEP(bots[cn]);
+ dorefresh = true;
+ }
+ bool deleteai() {
+ loopvrev(bots) if(bots[i] && bots[i]->ownernum >= 0) {
+ deleteai(bots[i]);
+ return true;
+ }
+ return false;
+ }
+ void reinitai(clientinfo *ci) {
+ if(ci->ownernum < 0) deleteai(ci);
+ else if(ci->aireinit >= 1) {
+ sendf(-1, 1, "ri6ss", N_INITAI, ci->clientnum, ci->ownernum, ci->state.aitype, ci->state.skill, ci->playermodel, ci->name, ci->team);
+ if(ci->aireinit == 2) {
+ ci->reassign();
+ if(ci->state.state==CS_ALIVE) sendspawn(ci);
+ else sendresume(ci);
+ }
+ ci->aireinit = 0;
+ }
+ }
+ void shiftai(clientinfo *ci, clientinfo *owner = NULL) {
+ clientinfo *prevowner = (clientinfo *)getclientinfo(ci->ownernum);
+ if(prevowner) prevowner->bots.removeobj(ci);
+ if(!owner) { ci->aireinit = 0; ci->ownernum = -1; }
+ else if(ci->ownernum != owner->clientnum) { ci->aireinit = 2; ci->ownernum = owner->clientnum; owner->bots.add(ci); }
+ dorefresh = true;
+ }
+ void removeai(clientinfo *ci) {
+ // either schedules a removal, or someone else to assign to
+ loopvrev(ci->bots) shiftai(ci->bots[i], findaiclient(ci));
+ }
+ bool reassignai() {
+ clientinfo *hi = NULL, *lo = NULL;
+ loopv(clients) {
+ clientinfo *ci = clients[i];
+ if(!validaiclient(ci)) continue;
+ if(!lo || ci->bots.length() < lo->bots.length()) lo = ci;
+ if(!hi || ci->bots.length() > hi->bots.length()) hi = ci;
+ }
+ if(hi && lo && hi->bots.length() - lo->bots.length() > 1) {
+ loopvrev(hi->bots) {
+ shiftai(hi->bots[i], lo);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void checksetup() {
+ loopvrev(bots) if(bots[i]) reinitai(bots[i]);
+ }
+ void clearai() {
+ // clear and remove all ai immediately
+ loopvrev(bots) if(bots[i]) deleteai(bots[i]);
+ }
+ void checkai() {
+ if(!dorefresh) return;
+ dorefresh = false;
+ if(m_botmode && numclients(-1, false, true)) {
+ checksetup();
+ while(reassignai());
+ }
+ else clearai();
+ }
+ void reqadd(clientinfo *ci, int skill) {
+ if(!ci->local && !ci->privilege) return;
+ if(!addai(skill, !ci->local && ci->privilege < PRIV_ADMIN ? botlimit : -1)) sendf(ci->clientnum, 1, "ris", N_SERVMSG, "failed to create or assign bot");
+ }
+ void reqdel(clientinfo *ci) {
+ if(!ci->local && !ci->privilege) return;
+ if(!deleteai()) sendf(ci->clientnum, 1, "ris", N_SERVMSG, "failed to remove any bots");
+ }
+ void setbotlimit(clientinfo *ci, int limit) {
+ if(ci && !ci->local && ci->privilege < PRIV_ADMIN) return;
+ botlimit = clamp(limit, 0, MAXBOTS);
+ dorefresh = true;
+ defformatstring(msg, "bot limit is now %d", botlimit);
+ sendservmsg(msg);
+ }
+ void changemap() {
+ dorefresh = true;
+ loopv(clients) if(clients[i]->local || clients[i]->privilege) return;
+ }
+ void addclient(clientinfo *ci) {
+ if(ci->state.aitype == AI_NONE) dorefresh = true;
+ }
+ void changeteam(clientinfo *ci) {
+ if(ci->state.aitype == AI_NONE) dorefresh = true;
+ }
+ }
}