Artifact 0b8b0d202d5d00004ca28e5d3cd76fdf4446c42be547e01fab7c4cee8f0b2eea:
- File
src/clientgame.m
— part of check-in
[75e920ae30]
at
2025-03-29 14:25:43
on branch trunk
— Switch from clang-format to manual formatting
clang-format does too many weird things. (user: js, size: 9909) [annotate] [blame] [check-ins using]
// 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" #import "Variable.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 (Player *player in players) if ([player isKindOfClass: Player.class]) player.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)); } COMMAND(map, ARG_1STR, ^ (OFString *name) { changemap(name); })