// clientgame.cpp: core game related stuff
#include "cube.h"
int nextmode = 0; // nextmode becomes gamemode after next map load
VAR(gamemode, 1, 0, 0);
void
mode(int n)
{
addmsg(1, 2, SV_GAMEMODE, nextmode = n);
}
COMMAND(mode, ARG_1INT)
bool intermission = false;
dynent *player1 = newdynent(); // our client
dvector players; // other clients
VARP(sensitivity, 0, 10, 10000);
VARP(sensitivityscale, 1, 1, 10000);
VARP(invmouse, 0, 0, 1);
int lastmillis = 0;
int curtime = 10;
OFString *clientmap;
OFString *
getclientmap()
{
return clientmap;
}
void
resetmovement(dynent *d)
{
d->k_left = false;
d->k_right = false;
d->k_up = false;
d->k_down = false;
d->jumpnext = false;
d->strafe = 0;
d->move = 0;
}
void
spawnstate(dynent *d) // reset player state not persistent accross spawns
{
resetmovement(d);
d->vel.x = d->vel.y = d->vel.z = 0;
d->onfloor = false;
d->timeinair = 0;
d->health = 100;
d->armour = 50;
d->armourtype = A_BLUE;
d->quadmillis = 0;
d->lastattackgun = d->gunselect = GUN_SG;
d->gunwait = 0;
d->attacking = false;
d->lastaction = 0;
loopi(NUMGUNS) d->ammo[i] = 0;
d->ammo[GUN_FIST] = 1;
if (m_noitems) {
d->gunselect = GUN_RIFLE;
d->armour = 0;
if (m_noitemsrail) {
d->health = 1;
d->ammo[GUN_RIFLE] = 100;
} else {
if (gamemode == 12) {
d->gunselect = GUN_FIST;
return;
} // eihrul's secret "instafist" mode
d->health = 256;
if (m_tarena) {
int gun1 = rnd(4) + 1;
baseammo(d->gunselect = gun1);
for (;;) {
int gun2 = rnd(4) + 1;
if (gun1 != gun2) {
baseammo(gun2);
break;
}
}
} else if (m_arena) // insta arena
{
d->ammo[GUN_RIFLE] = 100;
} else // efficiency
{
loopi(4) baseammo(i + 1);
d->gunselect = GUN_CG;
}
d->ammo[GUN_CG] /= 2;
}
} else {
d->ammo[GUN_SG] = 5;
}
}
dynent *
newdynent() // create a new blank player or monster
{
dynent *d = (dynent *)malloc(sizeof(dynent));
d->o.x = 0;
d->o.y = 0;
d->o.z = 0;
d->yaw = 270;
d->pitch = 0;
d->roll = 0;
d->maxspeed = 22;
d->outsidemap = false;
d->inwater = false;
d->radius = 1.1f;
d->eyeheight = 3.2f;
d->aboveeye = 0.7f;
d->frags = 0;
d->plag = 0;
d->ping = 0;
d->lastupdate = lastmillis;
d->enemy = NULL;
d->monsterstate = 0;
d->name[0] = d->team[0] = 0;
d->blocked = false;
d->lifesequence = 0;
d->state = CS_ALIVE;
spawnstate(d);
return d;
}
void
respawnself()
{
spawnplayer(player1);
showscores(false);
}
void
arenacount(dynent *d, int &alive, int &dead, char *&lastteam, bool &oneteam)
{
if (d->state != CS_DEAD) {
alive++;
if (lastteam && strcmp(lastteam, d->team))
oneteam = false;
lastteam = d->team;
} else {
dead++;
}
}
int arenarespawnwait = 0;
int arenadetectwait = 0;
void
arenarespawn()
{
if (arenarespawnwait) {
if (arenarespawnwait < lastmillis) {
arenarespawnwait = 0;
conoutf(@"new round starting... fight!");
respawnself();
}
} else if (arenadetectwait == 0 || arenadetectwait < lastmillis) {
arenadetectwait = 0;
int alive = 0, dead = 0;
char *lastteam = NULL;
bool oneteam = true;
loopv(players) if (players[i])
arenacount(players[i], alive, dead, lastteam, oneteam);
arenacount(player1, alive, dead, lastteam, oneteam);
if (dead > 0 && (alive <= 1 || (m_teammode && oneteam))) {
conoutf(
@"arena round is over! next round in 5 seconds...");
if (alive)
conoutf(
@"team %s is last man standing", lastteam);
else
conoutf(@"everyone died!");
arenarespawnwait = lastmillis + 5000;
arenadetectwait = lastmillis + 10000;
player1->roll = 0;
}
}
}
void
zapdynent(dynent *&d)
{
if (d)
free(d);
d = NULL;
}
extern int democlientnum;
void
otherplayers()
{
loopv(players) if (players[i])
{
const int lagtime = lastmillis - players[i]->lastupdate;
if (lagtime > 1000 && players[i]->state == CS_ALIVE) {
players[i]->state = CS_LAGGED;
continue;
}
if (lagtime && players[i]->state != CS_DEAD &&
(!demoplayback || i != democlientnum))
moveplayer(
players[i], 2, false); // use physics to extrapolate
// player position
}
}
void
respawn()
{
if (player1->state == CS_DEAD) {
player1->attacking = false;
if (m_arena) {
conoutf(@"waiting for new round to start...");
return;
}
if (m_sp) {
nextmode = gamemode;
changemap(clientmap);
return;
} // if we die in SP we try the same map again
respawnself();
}
}
int sleepwait = 0;
static OFString *sleepcmd = nil;
void
sleepf(OFString *msec, OFString *cmd)
{
sleepwait = (int)msec.longLongValue + lastmillis;
sleepcmd = cmd;
}
COMMANDN(sleep, sleepf, ARG_2STR)
void
updateworld(int millis) // main game update loop
{
if (lastmillis) {
curtime = millis - lastmillis;
if (sleepwait && lastmillis > sleepwait) {
sleepwait = 0;
execute(sleepcmd);
}
physicsframe();
checkquad(curtime);
if (m_arena)
arenarespawn();
moveprojectiles((float)curtime);
demoplaybackstep();
if (!demoplayback) {
if (getclientnum() >= 0)
shoot(player1, worldpos); // only shoot when
// connected to server
gets2c(); // do this first, so we have most accurate
// information when our player moves
}
otherplayers();
if (!demoplayback) {
monsterthink();
if (player1->state == CS_DEAD) {
if (lastmillis - player1->lastaction < 2000) {
player1->move = player1->strafe = 0;
moveplayer(player1, 10, false);
} else if (!m_arena && !m_sp &&
lastmillis - player1->lastaction > 10000)
respawn();
} else if (!intermission) {
moveplayer(player1, 20, true);
checkitems();
}
c2sinfo(player1); // do this last, to reduce the
// effective frame lag
}
}
lastmillis = millis;
}
void
entinmap(dynent *
d) // brute force but effective way to find a free spawn spot in the map
{
loopi(100) // try max 100 times
{
float dx = (rnd(21) - 10) / 10.0f * i; // increasing distance
float dy = (rnd(21) - 10) / 10.0f * i;
d->o.x += dx;
d->o.y += dy;
if (collide(d, true, 0, 0))
return;
d->o.x -= dx;
d->o.y -= dy;
}
conoutf(@"can't find entity spawn spot! (%d, %d)", (int)d->o.x,
(int)d->o.y);
// leave ent at original pos, possibly stuck
}
int spawncycle = -1;
int fixspawn = 2;
void
spawnplayer(dynent *d) // place at random spawn. also used by monsters!
{
int r = fixspawn-- > 0 ? 4 : rnd(10) + 1;
loopi(r) spawncycle = findentity(PLAYERSTART, spawncycle + 1);
if (spawncycle != -1) {
d->o.x = ents[spawncycle].x;
d->o.y = ents[spawncycle].y;
d->o.z = ents[spawncycle].z;
d->yaw = ents[spawncycle].attr1;
d->pitch = 0;
d->roll = 0;
} else {
d->o.x = d->o.y = (float)ssize / 2;
d->o.z = 4;
}
entinmap(d);
spawnstate(d);
d->state = CS_ALIVE;
}
// movement input code
#define dir(name, v, d, s, os) \
void name(bool isdown) \
{ \
player1->s = isdown; \
player1->v = isdown ? d : (player1->os ? -(d) : 0); \
player1->lastmove = lastmillis; \
}
dir(backward, move, -1, k_down, k_up);
dir(forward, move, 1, k_up, k_down);
dir(left, strafe, 1, k_left, k_right);
dir(right, strafe, -1, k_right, k_left);
void
attack(bool on)
{
if (intermission)
return;
if (editmode)
editdrag(on);
else if (player1->attacking = on)
respawn();
}
void
jumpn(bool on)
{
if (!intermission && (player1->jumpnext = on))
respawn();
}
COMMAND(backward, ARG_DOWN)
COMMAND(forward, ARG_DOWN)
COMMAND(left, ARG_DOWN)
COMMAND(right, ARG_DOWN)
COMMANDN(jump, jumpn, ARG_DOWN)
COMMAND(attack, ARG_DOWN)
COMMAND(showscores, ARG_DOWN)
void
fixplayer1range()
{
const float MAXPITCH = 90.0f;
if (player1->pitch > MAXPITCH)
player1->pitch = MAXPITCH;
if (player1->pitch < -MAXPITCH)
player1->pitch = -MAXPITCH;
while (player1->yaw < 0.0f)
player1->yaw += 360.0f;
while (player1->yaw >= 360.0f)
player1->yaw -= 360.0f;
}
void
mousemove(int dx, int dy)
{
if (player1->state == CS_DEAD || intermission)
return;
const float SENSF = 33.0f; // try match quake sens
player1->yaw += (dx / SENSF) * (sensitivity / (float)sensitivityscale);
player1->pitch -= (dy / SENSF) *
(sensitivity / (float)sensitivityscale) * (invmouse ? -1 : 1);
fixplayer1range();
}
// damage arriving from the network, monsters, yourself, all ends up here.
void
selfdamage(int damage, int actor, dynent *act)
{
if (player1->state != CS_ALIVE || editmode || intermission)
return;
damageblend(damage);
demoblend(damage);
int ad = damage * (player1->armourtype + 1) * 20 /
100; // let armour absorb when possible
if (ad > player1->armour)
ad = player1->armour;
player1->armour -= ad;
damage -= ad;
float droll = damage / 0.5f;
player1->roll += player1->roll > 0
? droll
: (player1->roll < 0
? -droll
: (rnd(2) ? droll
: -droll)); // give player a kick depending
// on amount of damage
if ((player1->health -= damage) <= 0) {
if (actor == -2) {
conoutf(@"you got killed by %s!", act->name);
} else if (actor == -1) {
actor = getclientnum();
conoutf(@"you suicided!");
addmsg(1, 2, SV_FRAGS, --player1->frags);
} else {
dynent *a = getclient(actor);
if (a) {
if (isteam(a->team, player1->team)) {
conoutf(@"you got fragged by a "
@"teammate (%s)",
a->name);
} else {
conoutf(
@"you got fragged by %s", a->name);
}
}
}
showscores(true);
addmsg(1, 2, SV_DIED, actor);
player1->lifesequence++;
player1->attacking = false;
player1->state = CS_DEAD;
player1->pitch = 0;
player1->roll = 60;
playsound(S_DIE1 + rnd(2));
spawnstate(player1);
player1->lastaction = lastmillis;
} else {
playsound(S_PAIN6);
}
}
void
timeupdate(int timeremain)
{
if (!timeremain) {
intermission = true;
player1->attacking = false;
conoutf(@"intermission:");
conoutf(@"game has ended!");
showscores(true);
} else {
conoutf(@"time remaining: %d minutes", timeremain);
}
}
dynent *
getclient(int cn) // ensure valid entity
{
if (cn < 0 || cn >= MAXCLIENTS) {
neterr(@"clientnum");
return NULL;
}
while (cn >= players.length())
players.add(NULL);
return players[cn] ? players[cn] : (players[cn] = newdynent());
}
void
initclient()
{
clientmap = @"";
initclientnet();
}
void
startmap(OFString *name) // called just after a map load
{
if (netmapstart() && m_sp) {
gamemode = 0;
conoutf(@"coop sp not supported yet");
}
sleepwait = 0;
monsterclear();
projreset();
spawncycle = -1;
spawnplayer(player1);
player1->frags = 0;
loopv(players) if (players[i]) players[i]->frags = 0;
resetspawns();
clientmap = name;
if (editmode)
toggleedit();
setvar(@"gamespeed", 100);
setvar(@"fog", 180);
setvar(@"fogcolour", 0x8099B3);
showscores(false);
intermission = false;
Cube.sharedInstance.framesInMap = 0;
conoutf(@"game mode is %@", modestr(gamemode));
}
COMMANDN(map, changemap, ARG_1STR)