Cube  Artifact [d1273c7a21]

Artifact d1273c7a2194b6088cc8d5c5d3b0acee103fdf7879a9647facc7e936ab752c2c:


// clientgame.cpp: core game related stuff

#include "cube.h"

#import "Command.h"
#import "DynamicEntity.h"
#import "Entity.h"
#import "Monster.h"
#import "OFString+Cube.h"
#import "Player.h"

int nextmode = 0; // nextmode becomes gamemode after next map load
VAR(gamemode, 1, 0, 0);

COMMAND(mode, ARG_1INT, ^(int n) {
	addmsg(1, 2, SV_GAMEMODE, nextmode = n);
})

bool intermission = false;

OFMutableArray *players; // other clients

void
initPlayers()
{
	players = [[OFMutableArray alloc] init];
}

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
respawnself()
{
	spawnplayer(Player.player1);
	showscores(false);
}

static void
arenacount(Player *d, int *alive, int *dead, OFString **lastteam, bool *oneteam)
{
	if (d.state != CS_DEAD) {
		(*alive)++;
		if (![*lastteam isEqual: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;
		OFString *lastteam = nil;
		bool oneteam = true;
		for (id player in players)
			if (player != [OFNull null])
				arenacount(
				    player, &alive, &dead, &lastteam, &oneteam);
		arenacount(Player.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;
			Player.player1.roll = 0;
		}
	}
}

extern int democlientnum;

void
otherplayers()
{
	[players
	    enumerateObjectsUsingBlock:^(Player *player, size_t i, bool *stop) {
		    if ([player isKindOfClass:Player.class])
			    return;

		    const int lagtime = lastmillis - player.lastUpdate;
		    if (lagtime > 1000 && player.state == CS_ALIVE) {
			    player.state = CS_LAGGED;
			    return;
		    }

		    if (lagtime && player.state != CS_DEAD &&
		        (!demoplayback || i != democlientnum))
			    // use physics to extrapolate player position
			    moveplayer(player, 2, false);
	    }];
}

void
respawn()
{
	if (Player.player1.state == CS_DEAD) {
		Player.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;
COMMAND(sleep, ARG_2STR, ^(OFString *msec, OFString *cmd) {
	sleepwait = msec.cube_intValue + lastmillis;
	sleepcmd = cmd;
})

void
updateworld(int millis) // main game update loop
{
	if (lastmillis) {
		curtime = millis - lastmillis;
		if (sleepwait && lastmillis > sleepwait) {
			sleepwait = 0;
			execute(sleepcmd, true);
		}
		physicsframe();
		checkquad(curtime);
		if (m_arena)
			arenarespawn();
		moveprojectiles((float)curtime);
		demoplaybackstep();
		Player *player1 = Player.player1;
		if (!demoplayback) {
			if (getclientnum() >= 0)
				// only shoot when connected to server
				shoot(player1, worldpos);
			// do this first, so we have most accurate information
			// when our player moves
			gets2c();
		}
		otherplayers();
		if (!demoplayback) {
			[Monster thinkAll];

			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();
			}

			// do this last, to reduce the effective frame lag
			c2sinfo(player1);
		}
	}
	lastmillis = millis;
}

// brute force but effective way to find a free spawn spot in the map
void
entinmap(DynamicEntity *d)
{
	// try max 100 times
	for (int i = 0; i < 100; i++) {
		float dx = (rnd(21) - 10) / 10.0f * i; // increasing distance
		float dy = (rnd(21) - 10) / 10.0f * i;
		OFVector3D old = d.origin;
		d.origin = OFAddVectors3D(d.origin, OFMakeVector3D(dx, dy, 0));
		if (collide(d, true, 0, 0))
			return;
		d.origin = old;
	}
	conoutf(@"can't find entity spawn spot! (%d, %d)", (int)d.origin.x,
	    (int)d.origin.y);
	// leave ent at original pos, possibly stuck
}

int spawncycle = -1;
int fixspawn = 2;

// place at random spawn. also used by monsters!
void
spawnplayer(Player *d)
{
	int r = fixspawn-- > 0 ? 4 : rnd(10) + 1;
	for (int i = 0; i < r; i++)
		spawncycle = findentity(PLAYERSTART, spawncycle + 1);
	if (spawncycle != -1) {
		d.origin = OFMakeVector3D(
		    ents[spawncycle].x, ents[spawncycle].y, ents[spawncycle].z);
		d.yaw = ents[spawncycle].attr1;
		d.pitch = 0;
		d.roll = 0;
	} else
		d.origin =
		    OFMakeVector3D((float)ssize / 2, (float)ssize / 2, 4);
	entinmap(d);
	[d resetToSpawnState];
	d.state = CS_ALIVE;
}

// movement input code

#define dir(name, v, d, s, os)                                    \
	COMMAND(name, ARG_DOWN, ^(bool isDown) {                  \
		Player *player1 = Player.player1;                 \
		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);

COMMAND(attack, ARG_DOWN, ^(bool on) {
	if (intermission)
		return;
	if (editmode)
		editdrag(on);
	else if ((Player.player1.attacking = on))
		respawn();
})

COMMAND(jump, ARG_DOWN, ^(bool on) {
	if (!intermission && (Player.player1.jumpNext = on))
		respawn();
})

COMMAND(showscores, ARG_DOWN, ^(bool isDown) {
	showscores(isDown);
})

void
fixplayer1range()
{
	const float MAXPITCH = 90.0f;
	Player *player1 = Player.player1;
	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)
{
	Player *player1 = Player.player1;
	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, DynamicEntity *act)
{
	Player *player1 = Player.player1;
	if (player1.state != CS_ALIVE || editmode || intermission)
		return;
	damageblend(damage);
	demoblend(damage);
	// let armour absorb when possible
	int ad = damage * (player1.armourType + 1) * 20 / 100;
	if (ad > Player.player1.armour)
		ad = Player.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 %@!", act.name);
		} else if (actor == -1) {
			actor = getclientnum();
			conoutf(@"you suicided!");
			addmsg(1, 2, SV_FRAGS, --player1.frags);
		} else {
			Player *a = getclient(actor);
			if (a != nil) {
				if (isteam(a.team, player1.team))
					conoutf(@"you got fragged by a "
					        @"teammate (%@)",
					    a.name);
				else
					conoutf(
					    @"you got fragged by %@", 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), NULL);
		[player1 resetToSpawnState];
		player1.lastAction = lastmillis;
	} else
		playsound(S_PAIN6, NULL);
}

void
timeupdate(int timeremain)
{
	if (!timeremain) {
		intermission = true;
		Player.player1.attacking = false;
		conoutf(@"intermission:");
		conoutf(@"game has ended!");
		showscores(true);
	} else {
		conoutf(@"time remaining: %d minutes", timeremain);
	}
}

Player *
getclient(int cn) // ensure valid entity
{
	if (cn < 0 || cn >= MAXCLIENTS) {
		neterr(@"clientnum");
		return nil;
	}

	while (cn >= players.count)
		[players addObject:[OFNull null]];

	id player = players[cn];
	if (player == [OFNull null]) {
		player = [Player player];
		players[cn] = player;
	}

	return player;
}

void
setclient(int cn, id client)
{
	if (cn < 0 || cn >= MAXCLIENTS)
		neterr(@"clientnum");
	while (cn >= players.count)
		[players addObject:[OFNull null]];
	players[cn] = client;
}

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;
	[Monster resetAll];
	projreset();
	spawncycle = -1;
	spawnplayer(Player.player1);
	Player.player1.frags = 0;
	for (id player in players)
		if (player != [OFNull null])
			[player setFrags: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));
}

COMMAND(map, ARG_1STR, ^(OFString *name) {
	changemap(name);
})