diff options
| author | xolatile | 2025-08-17 18:28:28 +0200 |
|---|---|---|
| committer | xolatile | 2025-08-17 18:28:28 +0200 |
| commit | bffe8d11bd1dfec49280fb64a17f0ae529ac3f5d (patch) | |
| tree | 9f4f7b6f5003585e5a170bd55ccaa335b8f26f90 /src/fpsgame | |
| parent | bec4167d29a68efd0cd2da36143e7f1c78a119a0 (diff) | |
| download | xolatile-badassbug-master.tar.xz xolatile-badassbug-master.tar.zst | |
Diffstat (limited to 'src/fpsgame')
| -rw-r--r-- | src/fpsgame/ai.cpp | 9 | ||||
| -rw-r--r-- | src/fpsgame/ai.h | 1 | ||||
| -rw-r--r-- | src/fpsgame/aiman.h | 212 | ||||
| -rw-r--r-- | src/fpsgame/client.cpp | 7 | ||||
| -rw-r--r-- | src/fpsgame/entities.cpp | 2 | ||||
| -rw-r--r-- | src/fpsgame/extinfo.h | 112 | ||||
| -rw-r--r-- | src/fpsgame/fps.cpp | 12 | ||||
| -rw-r--r-- | src/fpsgame/game.h | 8 | ||||
| -rw-r--r-- | src/fpsgame/render.cpp | 56 | ||||
| -rw-r--r-- | src/fpsgame/scoreboard.cpp | 28 | ||||
| -rw-r--r-- | src/fpsgame/server.cpp | 338 | ||||
| -rw-r--r-- | src/fpsgame/weapon.cpp | 14 |
12 files changed, 314 insertions, 485 deletions
diff --git a/src/fpsgame/ai.cpp b/src/fpsgame/ai.cpp index b59dbd3..5c74fe4 100644 --- a/src/fpsgame/ai.cpp +++ b/src/fpsgame/ai.cpp @@ -10,7 +10,6 @@ namespace ai { ICOMMAND(addbot, "s", (char *s), addmsg(N_ADDBOT, "ri", *s ? clamp(parseint(s), 1, 101) : -1)); ICOMMAND(delbot, "", (), addmsg(N_DELBOT, "r")); ICOMMAND(botlimit, "i", (int *n), addmsg(N_BOTLIMIT, "ri", *n)); - ICOMMAND(botbalance, "i", (int *n), addmsg(N_BOTBALANCE, "ri", *n)); float viewdist(int x) { return x <= 100 ? clamp((SIGHTMIN+(SIGHTMAX-SIGHTMIN))/100.f*float(x), float(SIGHTMIN), 10000.0f) : 10000.0f; } @@ -904,16 +903,13 @@ namespace ai { shoot(d, d->ai->target); } if(!intermission) { - if(d->ragdoll) cleanragdoll(d); moveplayer(d, 10, true); if(allowmove && !b.idle) timeouts(d); if(d->quadmillis) entities::checkquad(curtime, d); entities::checkitems(d); } - } - else if(d->state == CS_DEAD) { - if(d->ragdoll) moveragdoll(d); - else if(lastmillis-d->lastpain<2000) { + } else if(d->state == CS_DEAD) { + if(lastmillis-d->lastpain<2000) { d->move = d->strafe = 0; moveplayer(d, 10, false); } @@ -1074,4 +1070,3 @@ namespace ai { } } } - diff --git a/src/fpsgame/ai.h b/src/fpsgame/ai.h index 81b1e26..4328963 100644 --- a/src/fpsgame/ai.h +++ b/src/fpsgame/ai.h @@ -234,4 +234,3 @@ namespace ai { extern void itemspawned(int ent); extern void render(); } - diff --git a/src/fpsgame/aiman.h b/src/fpsgame/aiman.h deleted file mode 100644 index f926d44..0000000 --- a/src/fpsgame/aiman.h +++ /dev/null @@ -1,212 +0,0 @@ -// server-side ai manager -namespace aiman { - bool dorefresh = false, botbalance = true; - VARN(serverbotlimit, botlimit, 0, 8, MAXBOTS); - VAR(serverbotbalance, 0, 1, 1); - 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)); - } - } - void balanceteams() { - vector<teamscore> teams; - calcteams(teams); - vector<clientinfo *> reassign; - loopv(bots) if(bots[i]) reassign.add(bots[i]); - while(reassign.length() && teams.length() && teams[0].score > teams.last().score + 1) { - teamscore &t = teams.last(); - clientinfo *bot = NULL; - loopv(reassign) if(reassign[i] && !strcmp(reassign[i]->team, teams[0].team)) { - bot = reassign.removeunordered(i); - teams[0].score--; - t.score++; - for(int j = teams.length() - 2; j >= 0; j--) { - if(teams[j].score >= teams[j+1].score) break; - swap(teams[j], teams[j+1]); - } - break; - } - if(bot) { - copystring(bot->team, t.team, MAXTEAMLEN+1); - sendf(-1, 1, "riisi", N_SETTEAM, bot->clientnum, bot->team, 0); - } - else teams.remove(0, 1); - } - } - 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() { - if(m_teammode && botbalance) balanceteams(); - 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 setbotbalance(clientinfo *ci, bool balance) { - if(ci && !ci->local && !ci->privilege) return; - botbalance = balance ? 1 : 0; - dorefresh = true; - defformatstring(msg, "bot team balancing is now %s", botbalance ? "enabled" : "disabled"); - sendservmsg(msg); - } - - void changemap() { - dorefresh = true; - loopv(clients) if(clients[i]->local || clients[i]->privilege) return; - if(botbalance != (serverbotbalance != 0)) setbotbalance(NULL, serverbotbalance != 0); - } - void addclient(clientinfo *ci) { - if(ci->state.aitype == AI_NONE) dorefresh = true; - } - void changeteam(clientinfo *ci) { - if(ci->state.aitype == AI_NONE) dorefresh = true; - } -} diff --git a/src/fpsgame/client.cpp b/src/fpsgame/client.cpp index 83aa385..620c262 100644 --- a/src/fpsgame/client.cpp +++ b/src/fpsgame/client.cpp @@ -281,7 +281,6 @@ namespace game { if(i>=0) addmsg(N_SPECTATOR, "rii", i, val); } ICOMMAND(spectator, "is", (int *val, char *who), togglespectator(*val, who)); - ICOMMAND(checkmaps, "", (), addmsg(N_CHECKMAPS, "r")); int gamemode = INT_MAX, nextmode = INT_MAX; string clientmap = ""; void changemapserv(const char *name, int mode) { // forced map change from the server { @@ -1014,10 +1013,7 @@ namespace game { clientdisconnected(getint(p)); break; case N_SPAWN: { - if(d) { - if(d->state==CS_DEAD && d->lastpain) saveragdoll(d); - d->respawn(); - } + if(d) d->respawn(); parsestate(d, p); if(!d) break; d->state = CS_SPAWNING; @@ -1029,7 +1025,6 @@ namespace game { int scn = getint(p); fpsent *s = getclient(scn); if(!s) { parsestate(NULL, p); break; } - if(s->state==CS_DEAD && s->lastpain) saveragdoll(s); if(s==player1) { if(editmode) toggleedit(); stopfollowing(); diff --git a/src/fpsgame/entities.cpp b/src/fpsgame/entities.cpp index 656a9b0..f8c037c 100644 --- a/src/fpsgame/entities.cpp +++ b/src/fpsgame/entities.cpp @@ -93,7 +93,7 @@ namespace entities { if(mdlname) { vec p = e.o; p.z += 1+sinf(lastmillis/100.0+e.o.x+e.o.y)/20; - rendermodel(&e.light, mdlname, ANIM_MAPMODEL|ANIM_LOOP, p, lastmillis/(float)revs, 0, MDL_SHADOW | MDL_CULL_VFC | MDL_CULL_DIST | MDL_CULL_OCCLUDED); + rendermodel(&e.light, mdlname, ANIM_MAPMODEL|ANIM_LOOP, p, lastmillis/(float)revs, 0, MDL_CULL_VFC | MDL_CULL_DIST | MDL_CULL_OCCLUDED); } } } diff --git a/src/fpsgame/extinfo.h b/src/fpsgame/extinfo.h deleted file mode 100644 index b467a8c..0000000 --- a/src/fpsgame/extinfo.h +++ /dev/null @@ -1,112 +0,0 @@ - -#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 - -/* - Client: - ----- - A: 0 EXT_UPTIME - B: 0 EXT_PLAYERSTATS cn #a client number or -1 for all players# - C: 0 EXT_TEAMSCORE - Server: - -------- - A: 0 EXT_UPTIME EXT_ACK EXT_VERSION uptime #in seconds# - B: 0 EXT_PLAYERSTATS cn #send by client# EXT_ACK EXT_VERSION 0 or 1 #error, if cn was > -1 and client does not exist# ... - EXT_PLAYERSTATS_RESP_IDS pid(s) #1 packet# - EXT_PLAYERSTATS_RESP_STATS pid playerdata #1 packet for each player# - C: 0 EXT_TEAMSCORE EXT_ACK EXT_VERSION 0 or 1 #error, no teammode# remaining_time gamemode loop(teamdata [numbases bases] or -1) - Errors: - -------------- - B:C:default: 0 command EXT_ACK EXT_VERSION EXT_ERROR -*/ - 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); - } diff --git a/src/fpsgame/fps.cpp b/src/fpsgame/fps.cpp index d5501c4..0f0e3b1 100644 --- a/src/fpsgame/fps.cpp +++ b/src/fpsgame/fps.cpp @@ -139,8 +139,7 @@ namespace game { loopv(players) { fpsent *d = players[i]; if(d == player1 || d->ai) continue; - if(d->state==CS_DEAD && d->ragdoll) moveragdoll(d); - else if(!intermission) { + if(!intermission) { if(lastmillis - d->lastaction >= d->gunwait) d->gunwait = 0; if(d->quadmillis) entities::checkquad(curtime, d); } @@ -154,7 +153,7 @@ namespace game { if(smoothmove && d->smoothmillis>0) predictplayer(d, true); else moveplayer(d, 1, false); } - else if(d->state==CS_DEAD && !d->ragdoll && lastmillis-d->lastpain<2000) moveplayer(d, 1, true); + else if(d->state==CS_DEAD && lastmillis-d->lastpain<2000) moveplayer(d, 1, true); } } void checkslowmo() { @@ -176,18 +175,15 @@ namespace game { updateweapons(curtime); otherplayers(curtime); ai::update(); - moveragdolls(); gets2c(); if(connected) { if(player1->state == CS_DEAD) { - if(player1->ragdoll) moveragdoll(player1); - else if(lastmillis-player1->lastpain<2000) { + if(lastmillis-player1->lastpain<2000) { player1->move = player1->strafe = 0; moveplayer(player1, 10, true); } } else if(!intermission) { - if(player1->ragdoll) cleanragdoll(player1); moveplayer(player1, 10, true); swayhudgun(curtime); entities::checkitems(player1); @@ -201,7 +197,6 @@ namespace game { float a = x - lower, b = x - upper; return (b * b) / (a * a + b * b); } - static inline float harmonicmean(float a, float b) { return a + b > 0 ? 2 * a * b / (a + b) : 0.0f; } // avoid spawning near other players float ratespawn(dynent *d, const extentity &e) { fpsent *p = (fpsent *)d; @@ -427,7 +422,6 @@ namespace game { void startgame() { clearprojectiles(); clearbouncers(); - clearragdolls(); clearteaminfo(); // reset perma-state loopv(players) { diff --git a/src/fpsgame/game.h b/src/fpsgame/game.h index 92d4fd1..ed62052 100644 --- a/src/fpsgame/game.h +++ b/src/fpsgame/game.h @@ -139,7 +139,7 @@ enum { N_AUTHTRY, N_AUTHKICK, N_AUTHCHAL, N_AUTHANS, N_REQAUTH, N_PAUSEGAME, N_GAMESPEED, N_ADDBOT, N_DELBOT, N_INITAI, N_FROMAI, N_BOTLIMIT, N_BOTBALANCE, - N_MAPCRC, N_CHECKMAPS, + N_MAPCRC, N_SWITCHNAME, N_SWITCHMODEL, N_SWITCHTEAM, N_INITTOKENS, N_TAKETOKEN, N_EXPIRETOKENS, N_DROPTOKENS, N_DEPOSITTOKENS, N_STEALTOKENS, N_SERVCMD, @@ -166,7 +166,7 @@ static const int msgsizes[] = { // size inclusive message token, 0 for vari N_AUTHTRY, 0, N_AUTHKICK, 0, N_AUTHCHAL, 0, N_AUTHANS, 0, N_REQAUTH, 0, N_PAUSEGAME, 0, N_GAMESPEED, 0, N_ADDBOT, 2, N_DELBOT, 1, N_INITAI, 0, N_FROMAI, 2, N_BOTLIMIT, 2, N_BOTBALANCE, 2, - N_MAPCRC, 0, N_CHECKMAPS, 1, + N_MAPCRC, 0, N_SWITCHNAME, 0, N_SWITCHMODEL, 2, N_SWITCHTEAM, 0, N_INITTOKENS, 0, N_TAKETOKEN, 2, N_EXPIRETOKENS, 0, N_DROPTOKENS, 0, N_DEPOSITTOKENS, 2, N_STEALTOKENS, 0, N_SERVCMD, 0, @@ -587,12 +587,8 @@ namespace game { const char *ffa, *blueteam, *redteam, *hudguns, *vwep, *quad, *armour[3], *ffaicon, *blueicon, *redicon; - bool ragdoll; }; extern int playermodel, teamskins, testteam; - extern void saveragdoll(fpsent *d); - extern void clearragdolls(); - extern void moveragdolls(); extern const playermodelinfo &getplayermodelinfo(fpsent *d); extern void swayhudgun(int curtime); extern vec hudgunorigin(int gun, const vec &from, const vec &to, fpsent *d); diff --git a/src/fpsgame/render.cpp b/src/fpsgame/render.cpp index fff917e..dd8a5ee 100644 --- a/src/fpsgame/render.cpp +++ b/src/fpsgame/render.cpp @@ -6,42 +6,16 @@ extern float gatherspawninfos(dynent *d, int tag, vector<spawninfo> &spawninfos) namespace game { vector<fpsent *> bestplayers; vector<const char *> bestteams; - VARP(ragdoll, 0, 1, 1); - VARP(ragdollmillis, 0, 10000, 300000); - VARP(ragdollfade, 0, 1000, 300000); VARP(playermodel, 0, 0, 0); VARP(hidedead, 0, 0, 2); - vector<fpsent *> ragdolls; - void saveragdoll(fpsent *d) { - if(!d->ragdoll || !ragdollmillis || (!ragdollfade && lastmillis > d->lastpain + ragdollmillis)) return; - fpsent *r = new fpsent(*d); - r->lastupdate = ragdollfade && lastmillis > d->lastpain + max(ragdollmillis - ragdollfade, 0) ? lastmillis - max(ragdollmillis - ragdollfade, 0) : d->lastpain; - r->edit = NULL; - r->ai = NULL; - r->attackchan = r->idlechan = -1; - if(d==player1) r->playermodel = playermodel; - ragdolls.add(r); - d->ragdoll = NULL; - } - void clearragdolls() { - ragdolls.deletecontents(); - } - void moveragdolls() { - loopv(ragdolls) { - fpsent *d = ragdolls[i]; - if(lastmillis > d->lastupdate + ragdollmillis) { - delete ragdolls.remove(i--); - continue; - } - moveragdoll(d); - } - } - static const playermodelinfo playermodels[1] = { - { - "mrfixit", "mrfixit/blue", "mrfixit/red", "mrfixit/hudguns", NULL, "mrfixit/horns", { - "mrfixit/armor/blue", "mrfixit/armor/green", "mrfixit/armor/yellow" }, - "mrfixit", "mrfixit_blue", "mrfixit_red", true }, - }; + static const playermodelinfo playermodels[1] = { { + //~const char *ffa, *blueteam, *redteam, *hudguns, + //~*vwep, *quad, *armour[3], + //~*ffaicon, *blueicon, *redicon; + "mrfixit", "mrfixit/blue", "mrfixit/red", "mrfixit/hudguns", NULL, "mrfixit/horns", + { "mrfixit/armor/blue", "mrfixit/armor/green", "mrfixit/armor/yellow" }, + "mrfixit", "mrfixit_blue", "mrfixit_red" + } }; const playermodelinfo *getplayermodelinfo(int n) { (void) n; return &playermodels[0]; @@ -108,7 +82,7 @@ namespace game { case 1: mdlname = mdl.blueteam; break; case 2: mdlname = mdl.redteam; break; } - renderclient(d, mdlname, a[0].tag ? a : NULL, hold, attack, delay, lastaction, intermission && d->state!=CS_DEAD ? 0 : d->lastpain, fade, ragdoll && mdl.ragdoll); + renderclient(d, mdlname, a[0].tag ? a : NULL, hold, attack, delay, lastaction, intermission && d->state!=CS_DEAD ? 0 : d->lastpain, fade); } VARP(teamskins, 0, 0, 1); VARP(statusicons, 0, 1, 1); @@ -194,15 +168,6 @@ namespace game { renderstatusicons(d, team, offset); } } - loopv(ragdolls) { - fpsent *d = ragdolls[i]; - int team = 0; - if(teamskins || m_teammode) team = isteam(player1->team, d->team) ? 1 : 2; - float fade = 1.0f; - if(ragdollmillis && ragdollfade) - fade -= clamp(float(lastmillis - (d->lastupdate + max(ragdollmillis - ragdollfade, 0)))/min(ragdollmillis, ragdollfade), 0.0f, 1.0f); - renderplayer(d, getplayermodelinfo(d), team, fade, mainpass); - } if(isthirdperson() && !followingplayer() && (player1->state!=CS_DEAD || hidedead != 1)) renderplayer(player1, getplayermodelinfo(player1), teamskins || m_teammode ? 1 : 0, 1, mainpass); entities::renderentities(); renderbouncers(); @@ -212,7 +177,6 @@ namespace game { VARP(hudgun, 0, 1, 1); VARP(hudgunsway, 0, 1, 1); VARP(teamhudguns, 0, 1, 1); - VARP(chainsawhudgun, 0, 1, 1); VAR(testhudgun, 0, 0, 1); FVAR(swaystep, 1, 35.0f, 100); FVAR(swayside, 0, 0.04f, 1); @@ -263,7 +227,7 @@ namespace game { d->muzzle = vec(-1, -1, -1); a[0] = modelattach("tag_muzzle", &d->muzzle); dynent *interp = NULL; - if(d->gunselect==GUN_FIST && chainsawhudgun) { + if(d->gunselect==GUN_FIST) { anim |= ANIM_LOOP; base = 0; interp = &guninterp; diff --git a/src/fpsgame/scoreboard.cpp b/src/fpsgame/scoreboard.cpp index b1c7477..851b57d 100644 --- a/src/fpsgame/scoreboard.cpp +++ b/src/fpsgame/scoreboard.cpp @@ -334,23 +334,23 @@ namespace game { /// PW g.separator(); g.pushlist(); - g.textf(" %d%% ", 0x787878, "chainsaw.png", pwaccuracy[0]); - g.textf(" %d%% ", 0xfba6a6, "shotgun.png", pwaccuracy[1]); - g.textf(" %d%% ", 0x7bc77a, "chaingun.png", pwaccuracy[2]); - g.textf(" %d%% ", 0xefd7a6, "rocket_launcher.png", pwaccuracy[3]); - g.textf(" %d%% ", 0x8f91e7, "rifle.png", pwaccuracy[4]); - g.textf(" %d%% ", 0x9ee5e5, "grenade_launcher.png", pwaccuracy[5]); - g.textf(" %d%% ", 0xc3c3c3, "pistol.png", pwaccuracy[6]); + g.textf("%d%% ", 0x787878, "chainsaw.png", pwaccuracy[0]); + g.textf("%d%% ", 0xfba6a6, "shotgun.png", pwaccuracy[1]); + g.textf("%d%% ", 0x7bc77a, "chaingun.png", pwaccuracy[2]); + g.textf("%d%% ", 0xefd7a6, "rocket_launcher.png", pwaccuracy[3]); + g.textf("%d%% ", 0x8f91e7, "rifle.png", pwaccuracy[4]); + g.textf("%d%% ", 0x9ee5e5, "grenade_launcher.png", pwaccuracy[5]); + g.textf("%d%% ", 0xc3c3c3, "pistol.png", pwaccuracy[6]); g.poplist(); g.separator(); g.pushlist(); - g.textf(" x %d ", 0xffffff, "blue_armour.png", pwitemspicked[0]); - g.textf(" x %d ", 0xffffff, "green_armour.png", pwitemspicked[1]); - g.textf(" x %d ", 0xffffff, "yellow_armour.png", pwitemspicked[2]); - g.textf(" x %d ", 0xffffff, "tiny_health.png", pwitemspicked[3]); - g.textf(" x %d ", 0xffffff, "health.png", pwitemspicked[4]); - g.textf(" x %d ", 0xffffff, "health_boost.png", pwitemspicked[5]); - g.textf(" x %d ", 0xffffff, "quad_damage.png", pwitemspicked[6]); + g.textf("%d ", 0xffffff, "blue_armour.png", pwitemspicked[0]); + g.textf("%d ", 0xffffff, "green_armour.png", pwitemspicked[1]); + g.textf("%d ", 0xffffff, "yellow_armour.png", pwitemspicked[2]); + g.textf("%d ", 0xffffff, "tiny_health.png", pwitemspicked[3]); + g.textf("%d ", 0xffffff, "health.png", pwitemspicked[4]); + g.textf("%d ", 0xffffff, "health_boost.png", pwitemspicked[5]); + g.textf("%d ", 0xffffff, "quad_damage.png", pwitemspicked[6]); g.poplist(); } struct scoreboardgui : g3d_callback { 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; + } + } } diff --git a/src/fpsgame/weapon.cpp b/src/fpsgame/weapon.cpp index 30d2da8..4c85a36 100644 --- a/src/fpsgame/weapon.cpp +++ b/src/fpsgame/weapon.cpp @@ -408,14 +408,13 @@ namespace game { else p.o = v; } } - extern int chainsawhudgun; VARP(muzzleflash, 0, 1, 1); VARP(muzzlelight, 0, 1, 1); void shoteffects(int gun, const vec &from, const vec &to, fpsent *d, bool local, int id, int prevaction) { // create visual effect from a shot { int sound = guns[gun].sound, pspeed = 25; switch(gun) { case GUN_FIST: - if(d->type==ENT_PLAYER && chainsawhudgun) sound = S_CHAINSAW_ATTACK; + if(d->type==ENT_PLAYER) sound = S_CHAINSAW_ATTACK; break; case GUN_SG: { if(!local) createrays(gun, from, to); @@ -613,7 +612,7 @@ namespace game { } pitch = -bnc.roll; if(bnc.bouncetype==BNC_GRENADE) - rendermodel(&bnc.light, "projectiles/grenade", ANIM_MAPMODEL|ANIM_LOOP, pos, yaw, pitch, MDL_CULL_VFC|MDL_CULL_OCCLUDED|MDL_LIGHT|MDL_LIGHT_FAST|MDL_DYNSHADOW); + rendermodel(&bnc.light, "projectiles/grenade", ANIM_MAPMODEL|ANIM_LOOP, pos, yaw, pitch, MDL_CULL_VFC|MDL_CULL_OCCLUDED|MDL_LIGHT|MDL_LIGHT_FAST); else { const char *mdl = NULL; int cull = MDL_CULL_VFC|MDL_CULL_DIST|MDL_CULL_OCCLUDED; @@ -643,7 +642,7 @@ namespace game { int gun = -1; switch(d->attacksound) { case S_CHAINSAW_ATTACK: - if(chainsawhudgun) gun = GUN_FIST; + gun = GUN_FIST; break; default: return; @@ -660,7 +659,7 @@ namespace game { int sound = -1, radius = 0; if(d->clientnum >= 0 && d->state == CS_ALIVE) switch(d->gunselect) { case GUN_FIST: - if(chainsawhudgun && d->attacksound < 0) { + if(d->attacksound < 0) { sound = S_CHAINSAW_IDLE; radius = 50; } @@ -711,9 +710,10 @@ namespace game { /// Rough accuracy code, client-side only. int pwshotsfired [NUMGUNS] = { 0 }; -int pwshotshit [NUMGUNS] = { 0 }; +int pwshotshit [NUMGUNS] = { 0 }; int pwdamagedealt [NUMGUNS] = { 0 }; -int pwaccuracy [NUMGUNS] = { 0 }; +int pwaccuracy [NUMGUNS] = { 0 }; + int pwavgaccuracy = 0; void pwshot(int gun) { |
