git-svn-id: svn://svn.code.sf.net/p/actiongame/code/trunk/ac@2385 e016b1c5-ed68-44aa-a2f7-31cac88cffde
985 lines
26 KiB
C++
985 lines
26 KiB
C++
// clientgame.cpp: core game related stuff
|
|
|
|
#include "pch.h"
|
|
#include "cube.h"
|
|
#include "bot/bot.h"
|
|
|
|
int nextmode = 0; // nextmode becomes gamemode after next map load
|
|
VAR(gamemode, 1, 0, 0);
|
|
VARP(modeacronyms, 0, 0, 1);
|
|
|
|
flaginfo flaginfos[2];
|
|
|
|
void mode(int n)
|
|
{
|
|
if(m_mp(n) || !multiplayer()) addmsg(SV_GAMEMODE, "ri", nextmode = n);
|
|
}
|
|
COMMAND(mode, ARG_1INT);
|
|
|
|
bool intermission = false;
|
|
bool autoteambalance = false;
|
|
|
|
playerent *player1 = newplayerent(); // our client
|
|
vector<playerent *> players; // other clients
|
|
|
|
int lastmillis = 0, totalmillis = 0;
|
|
int curtime = 0;
|
|
string clientmap;
|
|
|
|
extern int framesinmap;
|
|
|
|
char *getclientmap() { return clientmap; }
|
|
|
|
void mapname() { result(getclientmap()); }
|
|
COMMAND(mapname, ARG_NONE);
|
|
|
|
extern bool c2sinit, senditemstoserver;
|
|
|
|
void setskin(playerent *pl, uint skin)
|
|
{
|
|
if(!pl) return;
|
|
if(pl == player1) c2sinit=false;
|
|
const int maxskin[2] = { 3, 5 };
|
|
pl->skin = skin % (maxskin[team_int(pl->team)]+1);
|
|
}
|
|
|
|
bool duplicatename(playerent *d, char *name = NULL)
|
|
{
|
|
if(!name) name = d->name;
|
|
if(d!=player1 && !strcmp(name, player1->name)) return true;
|
|
loopv(players) if(players[i] && d!=players[i] && !strcmp(name, players[i]->name)) return true;
|
|
return false;
|
|
}
|
|
|
|
char *colorname(playerent *d, int num, char *name, const char *prefix)
|
|
{
|
|
if(!name) name = d->name;
|
|
if(name[0] && !duplicatename(d, name)) return name;
|
|
static string cname[4];
|
|
s_sprintf(cname[num])("%s%s \fs\f6(%d)\fr", prefix, name, d->clientnum);
|
|
return cname[num];
|
|
}
|
|
|
|
char *colorping(int ping)
|
|
{
|
|
static string cping;
|
|
if(multiplayer(false)) s_sprintf(cping)("\fs\f%d%d\fr", ping <= 500 ? 0 : ping <= 1000 ? 2 : 3, ping);
|
|
else s_sprintf(cping)("%d", ping);
|
|
return cping;
|
|
}
|
|
|
|
void newname(const char *name)
|
|
{
|
|
if(name[0])
|
|
{
|
|
c2sinit = false;
|
|
filtertext(player1->name, name, false, MAXNAMELEN);
|
|
if(!player1->name[0]) s_strcpy(player1->name, "unarmed");
|
|
}
|
|
else conoutf("your name is: %s", player1->name);
|
|
alias("curname", player1->name);
|
|
}
|
|
|
|
int smallerteam()
|
|
{
|
|
int teamsize[2] = {0, 0};
|
|
loopv(players) if(players[i]) teamsize[team_int(players[i]->team)]++;
|
|
teamsize[team_int(player1->team)]++;
|
|
if(teamsize[0] == teamsize[1]) return -1;
|
|
return teamsize[0] < teamsize[1] ? 0 : 1;
|
|
}
|
|
|
|
void changeteam(int team, bool respawn) // force team and respawn
|
|
{
|
|
c2sinit = false;
|
|
if(m_ctf) tryflagdrop(NULL);
|
|
filtertext(player1->team, team_string(team), false, MAXTEAMLEN);
|
|
if(respawn) addmsg(SV_CHANGETEAM, "r");
|
|
}
|
|
|
|
void newteam(char *name)
|
|
{
|
|
if(name[0])
|
|
{
|
|
if(m_teammode)
|
|
{
|
|
if(!strcmp(name, player1->team)) return; // same team
|
|
if(!team_valid(name)) { conoutf("\f3\"%s\" is not a valid team name (try CLA or RVSF)", name); return; }
|
|
|
|
bool checkteamsize = autoteambalance && players.length() >= 1 && !m_botmode;
|
|
int freeteam = smallerteam();
|
|
|
|
if(team_valid(name))
|
|
{
|
|
int team = team_int(name);
|
|
if(checkteamsize && team != freeteam)
|
|
{
|
|
conoutf("\f3the %s team is already full", name);
|
|
return;
|
|
}
|
|
changeteam(team);
|
|
}
|
|
else changeteam(checkteamsize ? (uint)freeteam : rnd(2)); // random assignement
|
|
}
|
|
}
|
|
else conoutf("your team is: %s", player1->team);
|
|
}
|
|
|
|
void newskin(int skin) { player1->nextskin = skin; }
|
|
int curteam() { return team_int(player1->team); }
|
|
|
|
COMMANDN(team, newteam, ARG_1STR);
|
|
COMMANDN(name, newname, ARG_1STR);
|
|
COMMANDN(skin, newskin, ARG_1INT);
|
|
COMMAND(curteam, ARG_IVAL);
|
|
VARP(showscoresondeath, 0, 1, 1);
|
|
|
|
void deathstate(playerent *pl)
|
|
{
|
|
pl->state = CS_DEAD;
|
|
pl->spectatemode = SM_DEATHCAM;
|
|
pl->respawnoffset = pl->lastpain = pl->lastaction = lastmillis;
|
|
pl->move = pl->strafe = 0;
|
|
pl->pitch = pl->roll = 0;
|
|
pl->attacking = false;
|
|
pl->weaponsel->onownerdies();
|
|
|
|
if(pl == player1)
|
|
{
|
|
showscores(showscoresondeath!=0);
|
|
setscope(false);
|
|
if(editmode) toggleedit();
|
|
}
|
|
else pl->resetinterp();
|
|
}
|
|
|
|
void spawnstate(playerent *d) // reset player state not persistent accross spawns
|
|
{
|
|
d->respawn();
|
|
d->spawnstate(gamemode);
|
|
if(d==player1)
|
|
{
|
|
if(player1->skin!=player1->nextskin) setskin(player1, player1->nextskin);
|
|
setscope(false);
|
|
}
|
|
}
|
|
|
|
playerent *newplayerent() // create a new blank player
|
|
{
|
|
playerent *d = new playerent;
|
|
d->lastupdate = lastmillis;
|
|
setskin(d, rnd(6));
|
|
weapon::equipplayer(d); // flowtron : avoid overwriting d->spawnstate(gamemode) stuff from the following line (this used to be called afterwards)
|
|
spawnstate(d);
|
|
return d;
|
|
}
|
|
|
|
botent *newbotent() // create a new blank player
|
|
{
|
|
botent *d = new botent;
|
|
d->lastupdate = lastmillis;
|
|
setskin(d, rnd(6));
|
|
spawnstate(d);
|
|
weapon::equipplayer(d);
|
|
loopv(players) if(i!=getclientnum() && !players[i])
|
|
{
|
|
players[i] = d;
|
|
d->clientnum = i;
|
|
return d;
|
|
}
|
|
if(players.length()==getclientnum()) players.add(NULL);
|
|
d->clientnum = players.length();
|
|
players.add(d);
|
|
return d;
|
|
}
|
|
|
|
void freebotent(botent *d)
|
|
{
|
|
loopv(players) if(players[i]==d)
|
|
{
|
|
DELETEP(players[i]);
|
|
players.remove(i);
|
|
}
|
|
}
|
|
|
|
void zapplayer(playerent *&d)
|
|
{
|
|
DELETEP(d);
|
|
}
|
|
|
|
void movelocalplayer()
|
|
{
|
|
if(player1->state==CS_DEAD && !player1->allowmove())
|
|
{
|
|
if(lastmillis-player1->lastpain<2000)
|
|
{
|
|
player1->move = player1->strafe = 0;
|
|
moveplayer(player1, 10, false);
|
|
}
|
|
}
|
|
else if(!intermission)
|
|
{
|
|
moveplayer(player1, 10, true);
|
|
checkitems(player1);
|
|
}
|
|
}
|
|
|
|
// use physics to extrapolate player position
|
|
VARP(smoothmove, 0, 75, 100);
|
|
VARP(smoothdist, 0, 8, 16);
|
|
|
|
void predictplayer(playerent *d, bool move)
|
|
{
|
|
d->o = d->newpos;
|
|
d->yaw = d->newyaw;
|
|
d->pitch = d->newpitch;
|
|
if(move)
|
|
{
|
|
moveplayer(d, 1, false);
|
|
d->newpos = d->o;
|
|
}
|
|
float k = 1.0f - float(lastmillis - d->smoothmillis)/smoothmove;
|
|
if(k>0)
|
|
{
|
|
d->o.add(vec(d->deltapos).mul(k));
|
|
d->yaw += d->deltayaw*k;
|
|
if(d->yaw<0) d->yaw += 360;
|
|
else if(d->yaw>=360) d->yaw -= 360;
|
|
d->pitch += d->deltapitch*k;
|
|
}
|
|
}
|
|
|
|
void moveotherplayers()
|
|
{
|
|
loopv(players) if(players[i] && players[i]->type==ENT_PLAYER)
|
|
{
|
|
playerent *d = players[i];
|
|
const int lagtime = lastmillis-d->lastupdate;
|
|
if(!lagtime || intermission) continue;
|
|
else if(lagtime>1000 && d->state==CS_ALIVE)
|
|
{
|
|
d->state = CS_LAGGED;
|
|
continue;
|
|
}
|
|
if(d->state==CS_ALIVE || d->state==CS_EDITING)
|
|
{
|
|
if(smoothmove && d->smoothmillis>0) predictplayer(d, true);
|
|
else moveplayer(d, 1, false);
|
|
}
|
|
else if(d->state==CS_DEAD && lastmillis-d->lastpain<2000) moveplayer(d, 1, true);
|
|
}
|
|
}
|
|
|
|
struct scriptsleep { int wait; char *cmd; };
|
|
vector<scriptsleep> sleeps;
|
|
|
|
void addsleep(char *msec, char *cmd)
|
|
{
|
|
scriptsleep &s = sleeps.add();
|
|
s.wait = atoi(msec)+lastmillis;
|
|
s.cmd = newstring(cmd);
|
|
}
|
|
|
|
COMMANDN(sleep, addsleep, ARG_2STR);
|
|
|
|
void updateworld(int curtime, int lastmillis) // main game update loop
|
|
{
|
|
loopv(sleeps)
|
|
{
|
|
if(sleeps[i].wait && lastmillis > sleeps[i].wait)
|
|
{
|
|
execute(sleeps[i].cmd);
|
|
delete[] sleeps[i].cmd;
|
|
sleeps.remove(i--);
|
|
}
|
|
}
|
|
physicsframe();
|
|
checkweaponswitch();
|
|
checkakimbo();
|
|
if(getclientnum()>=0) shoot(player1, worldpos); // only shoot when connected to server
|
|
movebounceents();
|
|
moveotherplayers();
|
|
gets2c();
|
|
|
|
// Added by Rick: let bots think
|
|
if(m_botmode) BotManager.Think();
|
|
|
|
movelocalplayer();
|
|
c2sinfo(player1); // do this last, to reduce the effective frame lag
|
|
}
|
|
|
|
#define SECURESPAWNDIST 15
|
|
int spawncycle = -1;
|
|
int fixspawn = 2;
|
|
|
|
// returns -1 for a free place, else dist to the nearest enemy
|
|
float nearestenemy(vec place, char *team)
|
|
{
|
|
float nearestenemydist = -1;
|
|
loopv(players)
|
|
{
|
|
playerent *other = players[i];
|
|
if(!other || isteam(team, other->team)) continue;
|
|
float dist = place.dist(other->o);
|
|
if(dist < nearestenemydist || nearestenemydist == -1) nearestenemydist = dist;
|
|
}
|
|
if(nearestenemydist >= SECURESPAWNDIST || nearestenemydist < 0) return -1;
|
|
else return nearestenemydist;
|
|
}
|
|
|
|
void findplayerstart(playerent *d, bool mapcenter)
|
|
{
|
|
int r = fixspawn-->0 ? 4 : rnd(10)+1;
|
|
entity *e = NULL;
|
|
if(!mapcenter)
|
|
{
|
|
if(m_teammode || m_arena)
|
|
{
|
|
int type = m_teammode ? team_int(d->team) : 100;
|
|
loopi(r) spawncycle = findentity(PLAYERSTART, spawncycle+1, type);
|
|
if(spawncycle >= 0) e = &ents[spawncycle];
|
|
}
|
|
else
|
|
{
|
|
float bestdist = -1;
|
|
|
|
loopi(r)
|
|
{
|
|
spawncycle = findentity(PLAYERSTART, spawncycle+1);
|
|
if(spawncycle < 0) continue;
|
|
float dist = nearestenemy(vec(ents[spawncycle].x, ents[spawncycle].y, ents[spawncycle].z), d->team);
|
|
if(!e || dist < 0 || (bestdist >= 0 && dist > bestdist)) { e = &ents[spawncycle]; bestdist = dist; }
|
|
}
|
|
}
|
|
}
|
|
|
|
if(e)
|
|
{
|
|
d->o.x = e->x;
|
|
d->o.y = e->y;
|
|
d->o.z = e->z;
|
|
d->yaw = e->attr1;
|
|
d->pitch = 0;
|
|
d->roll = 0;
|
|
}
|
|
else
|
|
{
|
|
d->o.x = d->o.y = (float)ssize/2;
|
|
d->o.z = 4;
|
|
}
|
|
|
|
entinmap(d);
|
|
}
|
|
|
|
void spawnplayer(playerent *d)
|
|
{
|
|
d->respawn();
|
|
d->spawnstate(gamemode);
|
|
d->state = d==player1 && editmode ? CS_EDITING : CS_ALIVE;
|
|
findplayerstart(d);
|
|
}
|
|
|
|
void respawnself()
|
|
{
|
|
if(m_mp(gamemode)) addmsg(SV_TRYSPAWN, "r");
|
|
else
|
|
{
|
|
showscores(false);
|
|
setscope(false);
|
|
spawnplayer(player1);
|
|
player1->lifesequence++;
|
|
}
|
|
}
|
|
|
|
bool tryrespawn()
|
|
{
|
|
if(player1->state==CS_DEAD)
|
|
{
|
|
// set min wait time when in spectate mode (avoid unfair specting)
|
|
if(player1->isspectating() && player1->spectatemode!=SM_DEATHCAM && !m_arena)
|
|
{
|
|
spectate(SM_DEATHCAM);
|
|
player1->respawnoffset = lastmillis; // count again
|
|
}
|
|
|
|
int respawnmillis = player1->respawnoffset+(m_arena ? 0 : (m_ctf ? 5000 : 2000));
|
|
|
|
if(lastmillis>respawnmillis)
|
|
{
|
|
player1->attacking = false;
|
|
if(m_arena) { hudoutf("waiting for new round to start..."); return false; }
|
|
respawnself();
|
|
player1->weaponswitch(player1->primweap);
|
|
player1->lastaction -= weapon::weaponchangetime/2;
|
|
return true;
|
|
}
|
|
else hudoutf("wait %3.1f seconds to respawn", (respawnmillis-lastmillis)/(float)1000);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// damage arriving from the network, monsters, yourself, all ends up here.
|
|
|
|
void dodamage(int damage, playerent *pl, playerent *actor, bool gib, bool local)
|
|
{
|
|
if(pl->state != CS_ALIVE || intermission) return;
|
|
|
|
pl->respawnoffset = pl->lastpain = lastmillis;
|
|
if(local) damage = pl->dodamage(damage);
|
|
else if(actor==player1) return;
|
|
|
|
if(pl==player1)
|
|
{
|
|
updatedmgindicator(actor->o);
|
|
pl->damageroll(damage);
|
|
}
|
|
else damageeffect(damage, pl);
|
|
|
|
if(pl->health<=0) { if(local) dokill(pl, actor, gib); }
|
|
else if(pl==player1) playsound(S_PAIN6);
|
|
else playsound(S_PAIN1+rnd(5), pl);
|
|
}
|
|
|
|
void dokill(playerent *pl, playerent *act, bool gib)
|
|
{
|
|
if(pl->state!=CS_ALIVE || intermission) return;
|
|
|
|
string pname, aname, death;
|
|
s_strcpy(pname, pl==player1 ? "you" : colorname(pl));
|
|
s_strcpy(aname, act==player1 ? "you" : colorname(act));
|
|
s_strcpy(death, gib ? "gibbed" : "fragged");
|
|
void (*outf)(const char *s, ...) = (pl == player1 || act == player1) ? hudoutf : conoutf;
|
|
|
|
if(pl==act)
|
|
outf("\f2%s suicided%s", pname, pl==player1 ? "!" : "");
|
|
else if(isteam(pl->team, act->team))
|
|
{
|
|
if(pl==player1) outf("\f2you got %s by teammate %s", death, aname);
|
|
else outf("%s%s %s teammate %s", act==player1 ? "\f3" : "\f2", aname, death, pname);
|
|
}
|
|
else
|
|
{
|
|
if(pl==player1) outf("\f2you got %s by %s", death, aname);
|
|
else outf("\f2%s %s %s", aname, death, pname);
|
|
}
|
|
|
|
if(m_ctf && pl==player1) tryflagdrop(pl!=act && isteam(act->team, pl->team));
|
|
|
|
if(gib)
|
|
{
|
|
if(pl!=act && act->weaponsel->type == GUN_SNIPER) playsound(S_HEADSHOT, SP_LOW);
|
|
addgib(pl);
|
|
}
|
|
|
|
if(!m_mp(gamemode))
|
|
{
|
|
if(pl==act || isteam(pl->team, act->team)) act->frags--;
|
|
else act->frags += gib ? 2 : 1;
|
|
}
|
|
|
|
deathstate(pl);
|
|
pl->deaths++;
|
|
playsound(S_DIE1+rnd(2), pl);
|
|
}
|
|
|
|
VAR(minutesremaining, 1, 0, 0);
|
|
|
|
void timeupdate(int timeremain)
|
|
{
|
|
minutesremaining = timeremain;
|
|
if(!timeremain)
|
|
{
|
|
intermission = true;
|
|
player1->attacking = false;
|
|
conoutf("intermission:");
|
|
conoutf("game has ended!");
|
|
showscores(true);
|
|
execute("start_intermission");
|
|
}
|
|
else
|
|
{
|
|
conoutf("time remaining: %d minutes", timeremain);
|
|
if(timeremain==1)
|
|
{
|
|
musicsuggest(M_LASTMINUTE1 + rnd(2), 70*1000, true);
|
|
hudoutf("1 minute left!");
|
|
}
|
|
}
|
|
}
|
|
|
|
playerent *newclient(int cn) // ensure valid entity
|
|
{
|
|
if(cn<0 || cn>=MAXCLIENTS)
|
|
{
|
|
neterr("clientnum");
|
|
return NULL;
|
|
}
|
|
while(cn>=players.length()) players.add(NULL);
|
|
playerent *d = players[cn];
|
|
if(d) return d;
|
|
d = newplayerent();
|
|
players[cn] = d;
|
|
d->clientnum = cn;
|
|
return d;
|
|
}
|
|
|
|
playerent *getclient(int cn) // ensure valid entity
|
|
{
|
|
return players.inrange(cn) ? players[cn] : NULL;
|
|
}
|
|
|
|
void initclient()
|
|
{
|
|
clientmap[0] = 0;
|
|
newname("unarmed");
|
|
changeteam(rnd(2), false);
|
|
}
|
|
|
|
entity flagdummies[2]; // in case the map does not provide flags
|
|
|
|
void preparectf(bool cleanonly=false)
|
|
{
|
|
loopi(2) flaginfos[i].flag = &flagdummies[i];
|
|
if(!cleanonly)
|
|
{
|
|
loopv(ents)
|
|
{
|
|
entity &e = ents[i];
|
|
if(e.type==CTF_FLAG)
|
|
{
|
|
e.spawned = true;
|
|
if(e.attr2>2) { conoutf("\f3invalid ctf-flag entity (%i)", i); e.attr2 = 0; }
|
|
flaginfo &f = flaginfos[e.attr2];
|
|
f.ack = false;
|
|
f.actor = NULL;
|
|
f.actor_cn = -1;
|
|
f.team = e.attr2;
|
|
f.flag = &e;
|
|
f.state = CTFF_INBASE;
|
|
f.originalpos.x = (float) e.x;
|
|
f.originalpos.y = (float) e.y;
|
|
f.originalpos.z = (float) e.z;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int suicided = -1;
|
|
|
|
void startmap(const char *name) // called just after a map load
|
|
{
|
|
clearminimap();
|
|
senditemstoserver = true;
|
|
// Added by Rick
|
|
if(m_botmode) BotManager.BeginMap(name);
|
|
else kickallbots();
|
|
// End add by Rick
|
|
clearbounceents();
|
|
resetspawns();
|
|
if(m_ctf) preparectf();
|
|
particlereset();
|
|
suicided = -1;
|
|
spawncycle = -1;
|
|
if(m_mp(gamemode)) respawnself();
|
|
else findplayerstart(player1);
|
|
player1->frags = player1->flagscore = player1->deaths = player1->lifesequence = 0;
|
|
loopv(players) if(players[i]) players[i]->frags = players[i]->flagscore = players[i]->deaths = players[i]->lifesequence = 0;
|
|
s_strcpy(clientmap, name);
|
|
if(editmode) toggleedit();
|
|
setvar("gamespeed", 100);
|
|
setvar("paused", 0);
|
|
setvar("fog", 180);
|
|
setvar("fogcolour", 0x8099B3);
|
|
showscores(false);
|
|
intermission = false;
|
|
minutesremaining = -1;
|
|
if(*clientmap) conoutf("game mode is \"%s\"", modestr(gamemode, modeacronyms > 0));
|
|
if(firstrun) // runs once
|
|
{
|
|
execfile("config/firstrun.cfg");
|
|
firstrun = false;
|
|
}
|
|
}
|
|
|
|
void suicide()
|
|
{
|
|
if(player1->state!=CS_ALIVE) return;
|
|
if(!m_mp(gamemode)) dokill(player1, player1);
|
|
else if(suicided!=player1->lifesequence)
|
|
{
|
|
tryflagdrop(false);
|
|
addmsg(SV_SUICIDE, "r");
|
|
suicided = player1->lifesequence;
|
|
}
|
|
}
|
|
|
|
COMMAND(suicide, ARG_NONE);
|
|
|
|
// console and audio feedback
|
|
|
|
void flagmsg(int flag, int action)
|
|
{
|
|
flaginfo &f = flaginfos[flag];
|
|
if(!f.actor || !f.ack) return;
|
|
bool own = flag == team_int(player1->team), firstperson = f.actor == player1;
|
|
const char *teamstr = flag == team_int(player1->team) ? "your" : "the enemy";
|
|
|
|
switch(action)
|
|
{
|
|
case SV_FLAGPICKUP:
|
|
{
|
|
playsound(S_FLAGPICKUP, SP_HIGH);
|
|
if(firstperson)
|
|
{
|
|
conoutf("\f2you got the enemy flag");
|
|
musicsuggest(M_FLAGGRAB, 90*1000, true);
|
|
}
|
|
else conoutf("\f2%s got %s flag", colorname(f.actor), teamstr);
|
|
break;
|
|
}
|
|
case SV_FLAGDROP:
|
|
{
|
|
playsound(S_FLAGDROP, SP_HIGH);
|
|
if(firstperson)
|
|
{
|
|
conoutf("\f2you lost the flag");
|
|
musicfadeout(M_FLAGGRAB);
|
|
}
|
|
else conoutf("\f2%s lost %s flag", colorname(f.actor), teamstr);
|
|
break;
|
|
}
|
|
case SV_FLAGRETURN:
|
|
{
|
|
playsound(S_FLAGRETURN, SP_HIGH);
|
|
if(firstperson) conoutf("\f2you returned your flag");
|
|
else conoutf("\f2%s returned %s flag", colorname(f.actor), teamstr);
|
|
break;
|
|
}
|
|
case SV_FLAGSCORE:
|
|
{
|
|
playsound(S_FLAGSCORE, SP_HIGH);
|
|
if(firstperson)
|
|
{
|
|
conoutf("\f2you scored");
|
|
addmsg(SV_FLAGS, "ri", ++player1->flagscore);
|
|
musicfadeout(M_FLAGGRAB);
|
|
}
|
|
else conoutf("\f2%s scored for %s team", colorname(f.actor), (own ? "the enemy" : "your"));
|
|
break;
|
|
}
|
|
case SV_FLAGRESET:
|
|
{
|
|
playsound(S_FLAGRETURN, SP_HIGH);
|
|
conoutf("the server reset the flag");
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
void dropflag() { tryflagdrop(false); }
|
|
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 *msg = msgs[type];
|
|
char *out = newstring(_MAXDEFSTR);
|
|
out[_MAXDEFSTR] = '\0';
|
|
switch(type)
|
|
{
|
|
case SA_KICK:
|
|
case SA_BAN:
|
|
case SA_FORCETEAM:
|
|
case SA_GIVEADMIN:
|
|
{
|
|
int cn = atoi(arg1);
|
|
playerent *p = (cn == getclientnum() ? player1 : getclient(cn));
|
|
if(!p) break;
|
|
s_sprintf(out)(msg, colorname(p));
|
|
break;
|
|
}
|
|
case SA_MASTERMODE:
|
|
s_sprintf(out)(msg, atoi(arg1) == 0 ? "Open" : "Private");
|
|
break;
|
|
case SA_AUTOTEAM:
|
|
case SA_RECORDDEMO:
|
|
s_sprintf(out)(msg, atoi(arg1) == 0 ? "disable" : "enable");
|
|
break;
|
|
case SA_MAP:
|
|
s_sprintf(out)(msg, arg1, modestr(atoi(arg2), modeacronyms > 0));
|
|
break;
|
|
case SA_SERVERDESC:
|
|
s_sprintf(out)(msg, arg1);
|
|
break;
|
|
default:
|
|
s_sprintf(out)(msg, arg1, arg2);
|
|
break;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
votedisplayinfo *newvotedisplayinfo(playerent *owner, int type, char *arg1, char *arg2)
|
|
{
|
|
if(type < 0 || type >= SA_NUM) return NULL;
|
|
votedisplayinfo *v = new votedisplayinfo();
|
|
v->owner = owner;
|
|
v->type = type;
|
|
v->millis = lastmillis + (30+10)*1000;
|
|
char *votedesc = votestring(type, arg1, arg2);
|
|
s_strcpy(v->desc, votedesc);
|
|
DELETEA(votedesc);
|
|
return v;
|
|
}
|
|
|
|
votedisplayinfo *curvote = NULL, *calledvote = NULL;
|
|
|
|
void callvote(int type, char *arg1, char *arg2)
|
|
{
|
|
if(calledvote) return;
|
|
votedisplayinfo *v = newvotedisplayinfo(player1, type, arg1, arg2);
|
|
if(v)
|
|
{
|
|
calledvote = v;
|
|
ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
|
|
ucharbuf p(packet->data, packet->dataLength);
|
|
putint(p, SV_CALLVOTE);
|
|
putint(p, v->type);
|
|
switch(v->type)
|
|
{
|
|
case SA_MAP:
|
|
sendstring(arg1, p);
|
|
putint(p, nextmode);
|
|
break;
|
|
case SA_SERVERDESC:
|
|
sendstring(arg1, p);
|
|
break;
|
|
case SA_STOPDEMO:
|
|
case SA_REMBANS:
|
|
break;
|
|
default:
|
|
putint(p, atoi(arg1));
|
|
break;
|
|
}
|
|
enet_packet_resize(packet, p.length());
|
|
sendpackettoserv(1, packet);
|
|
}
|
|
else conoutf("\f3invalid vote");
|
|
}
|
|
|
|
void scallvote(char *type, char *arg1, char *arg2)
|
|
{
|
|
if(type)
|
|
{
|
|
int t = atoi(type);
|
|
if(t==SA_MAP) // FIXME
|
|
{
|
|
string n;
|
|
itoa(n, nextmode);
|
|
callvote(t, arg1, n);
|
|
}
|
|
else callvote(t, arg1, arg2);
|
|
}
|
|
}
|
|
|
|
void vote(int v)
|
|
{
|
|
if(!curvote || v < 0 || v >= VOTE_NUM) return;
|
|
if(curvote->localplayervoted) { conoutf("\f3you voted already"); return; }
|
|
ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
|
|
ucharbuf p(packet->data, packet->dataLength);
|
|
putint(p, SV_VOTE);
|
|
putint(p, v);
|
|
enet_packet_resize(packet, p.length());
|
|
sendpackettoserv(1, packet);
|
|
if(!curvote) { /*printf(":: curvote vanished!\n");*/ return; } // flowtron - happens when I call "/stopdemo"! .. seems the map-load happens in-between
|
|
curvote->stats[v]++;
|
|
curvote->localplayervoted = true;
|
|
}
|
|
|
|
void displayvote(votedisplayinfo *v)
|
|
{
|
|
if(!v) return;
|
|
DELETEP(curvote);
|
|
curvote = v;
|
|
conoutf("%s called a vote: %s", v->owner ? colorname(v->owner) : "", curvote->desc);
|
|
playsound(S_CALLVOTE, SP_HIGH);
|
|
curvote->localplayervoted = false;
|
|
}
|
|
|
|
void callvotesuc()
|
|
{
|
|
if(!calledvote) return;
|
|
displayvote(calledvote);
|
|
calledvote = NULL;
|
|
vote(VOTE_YES); // not automatically done by callvote to keep a clear sequence
|
|
}
|
|
|
|
void callvoteerr(int e)
|
|
{
|
|
if(e < 0 || e >= VOTEE_NUM) return;
|
|
conoutf("\f3could not vote: %s", voteerrorstr(e));
|
|
DELETEP(calledvote);
|
|
}
|
|
|
|
void votecount(int v) { if(curvote && v >= 0 && v < VOTE_NUM) curvote->stats[v]++; }
|
|
void voteresult(int v)
|
|
{
|
|
if(curvote && v >= 0 && v < VOTE_NUM)
|
|
{
|
|
curvote->result = v;
|
|
curvote->millis = lastmillis + 5000;
|
|
conoutf("vote %s", v == VOTE_YES ? "passed" : "failed");
|
|
if(multiplayer(false)) playsound(v == VOTE_YES ? S_VOTEPASS : S_VOTEFAIL, SP_HIGH);
|
|
}
|
|
}
|
|
|
|
void clearvote() { DELETEP(curvote); DELETEP(calledvote); }
|
|
|
|
COMMANDN(callvote, scallvote, ARG_3STR); //fixme,ah
|
|
COMMAND(vote, ARG_1INT);
|
|
|
|
void whois(int cn)
|
|
{
|
|
addmsg(SV_WHOIS, "ri", cn);
|
|
}
|
|
|
|
COMMAND(whois, ARG_1INT);
|
|
|
|
void setadmin(char *claim, char *password)
|
|
{
|
|
if(!claim) return;
|
|
if(strlen(password) > 15) { conoutf("the admin password has a maximum length of 15 characters"); return; }
|
|
else addmsg(SV_SETADMIN, "ris", atoi(claim) != 0 ? 1 : 0, password);
|
|
}
|
|
|
|
COMMAND(setadmin, ARG_2STR);
|
|
|
|
void changemap(const char *name) // silently request map change, server may ignore
|
|
{
|
|
ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
|
|
ucharbuf p(packet->data, packet->dataLength);
|
|
putint(p, SV_NEXTMAP);
|
|
sendstring(name, p);
|
|
putint(p, nextmode);
|
|
enet_packet_resize(packet, p.length());
|
|
sendpackettoserv(1, packet);
|
|
}
|
|
|
|
struct mline { string name, cmd; };
|
|
static vector<mline> mlines;
|
|
|
|
void *kickmenu = NULL, *banmenu = NULL, *forceteammenu = NULL, *giveadminmenu = NULL;
|
|
|
|
void refreshsopmenu(void *menu, bool init)
|
|
{
|
|
int item = 0;
|
|
mlines.setsize(0);
|
|
loopv(players) if(players[i])
|
|
{
|
|
mline &m = mlines.add();
|
|
s_strcpy(m.name, colorname(players[i]));
|
|
s_sprintf(m.cmd)("%s %d", menu==kickmenu ? "kick" : (menu==banmenu ? "ban" : (menu==forceteammenu ? "forceteam" : "giveadmin")), i);
|
|
menumanual(menu, item++, m.name, m.cmd);
|
|
}
|
|
}
|
|
|
|
playerent *updatefollowplayer(int shiftdirection)
|
|
{
|
|
if(!shiftdirection)
|
|
{
|
|
playerent *f = players.inrange(player1->followplayercn) ? players[player1->followplayercn] : NULL;
|
|
if(f) return f;
|
|
}
|
|
|
|
vector<playerent *> available;
|
|
loopv(players) if(players[i])
|
|
{
|
|
if(m_teammode && !isteam(players[i]->team, player1->team)) continue;
|
|
if(players[i]->state==CS_DEAD) continue;
|
|
available.add(players[i]);
|
|
}
|
|
if(!available.length()) return NULL;
|
|
|
|
int oldidx = -1;
|
|
if(players.inrange(player1->followplayercn)) oldidx = available.find(players[player1->followplayercn]);
|
|
if(oldidx<0) oldidx = 0;
|
|
int idx = (oldidx+shiftdirection) % available.length();
|
|
if(idx<0) idx += available.length();
|
|
|
|
player1->followplayercn = available[idx]->clientnum;
|
|
return players[player1->followplayercn];
|
|
}
|
|
|
|
void spectate(int mode) // set new spect mode
|
|
{
|
|
if(!player1->isspectating()) return;
|
|
if(mode == player1->spectatemode) return;
|
|
showscores(false);
|
|
switch(mode)
|
|
{
|
|
case SM_FOLLOW1ST:
|
|
case SM_FOLLOW3RD:
|
|
case SM_FOLLOW3RD_TRANSPARENT:
|
|
{
|
|
if(players.length() && updatefollowplayer()) break;
|
|
else mode = SM_FLY;
|
|
}
|
|
case SM_FLY:
|
|
{
|
|
if(player1->spectatemode != SM_FLY)
|
|
{
|
|
// set spectator location to last followed player
|
|
playerent *f = updatefollowplayer();
|
|
if(f)
|
|
{
|
|
player1->o = f->o;
|
|
player1->yaw = f->yaw;
|
|
player1->pitch = 0.0f;
|
|
player1->resetinterp();
|
|
}
|
|
else entinmap(player1); // or drop 'em at a random place
|
|
}
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
player1->spectatemode = mode;
|
|
}
|
|
|
|
|
|
void togglespect() // cycle through all spectating modes
|
|
{
|
|
if(m_botmode) spectate(SM_FLY);
|
|
else
|
|
{
|
|
int mode;
|
|
if(player1->spectatemode==SM_NONE) mode = SM_FOLLOW1ST; // start with 1st person spect
|
|
else mode = SM_FOLLOW1ST + (((player1->spectatemode-1) % (SM_NUM-SM_FOLLOW1ST)));
|
|
spectate(mode);
|
|
}
|
|
}
|
|
|
|
void changefollowplayer(int shift)
|
|
{
|
|
updatefollowplayer(shift);
|
|
}
|
|
|
|
COMMAND(spectate, ARG_1INT);
|
|
COMMAND(togglespect, ARG_NONE);
|
|
COMMAND(changefollowplayer, ARG_1INT);
|
|
|
|
int isalive() { return player1->state==CS_ALIVE ? 1 : 0; }
|
|
COMMANDN(alive, isalive, ARG_NONE);
|
|
|
|
void serverextension(char *ext, char *args)
|
|
{
|
|
if(!ext || !ext[0]) return;
|
|
size_t n = args ? strlen(args)+1 : 0;
|
|
if(n>0) addmsg(SV_EXTENSION, "rsis", ext, n, args);
|
|
else addmsg(SV_EXTENSION, "rsi", ext, n);
|
|
}
|
|
|
|
COMMAND(serverextension, ARG_2STR);
|
|
|