1 // clientgame.cpp: core game related stuff
2 
3 #include "cube.h"
4 
5 int nextmode = 0;         // nextmode becomes gamemode after next map load
6 VAR(gamemode, 1, 0, 0);
7 
mode(int n)8 void mode(int n) { addmsg(1, 2, SV_GAMEMODE, nextmode = n); };
9 COMMAND(mode, ARG_1INT);
10 
11 bool intermission = false;
12 
13 dynent *player1 = newdynent();          // our client
14 dvector players;                        // other clients
15 
16 VARP(sensitivity, 0, 10, 10000);
17 VARP(sensitivityscale, 1, 1, 10000);
18 VARP(invmouse, 0, 0, 1);
19 
20 int lastmillis = 0;
21 int curtime = 10;
22 string clientmap;
23 
24 extern int framesinmap;
25 
getclientmap()26 char *getclientmap() { return clientmap; };
27 
resetmovement(dynent * d)28 void resetmovement(dynent *d)
29 {
30     d->k_left = false;
31     d->k_right = false;
32     d->k_up = false;
33     d->k_down = false;
34     d->jumpnext = false;
35     d->strafe = 0;
36     d->move = 0;
37 };
38 
spawnstate(dynent * d)39 void spawnstate(dynent *d)              // reset player state not persistent accross spawns
40 {
41     resetmovement(d);
42     d->vel.x = d->vel.y = d->vel.z = 0;
43     d->onfloor = false;
44     d->timeinair = 0;
45     d->health = 100;
46     d->armour = 50;
47     d->armourtype = A_BLUE;
48     d->quadmillis = 0;
49     d->lastattackgun = d->gunselect = GUN_SG;
50     d->gunwait = 0;
51 	d->attacking = false;
52     d->lastaction = 0;
53     loopi(NUMGUNS) d->ammo[i] = 0;
54     d->ammo[GUN_FIST] = 1;
55     if(m_noitems)
56     {
57         d->gunselect = GUN_RIFLE;
58         d->armour = 0;
59         if(m_noitemsrail)
60         {
61             d->health = 1;
62             d->ammo[GUN_RIFLE] = 100;
63         }
64         else
65         {
66             if(gamemode==12) { d->gunselect = GUN_FIST; return; };  // eihrul's secret "instafist" mode
67             d->health = 256;
68             if(m_tarena)
69             {
70                 int gun1 = rnd(4)+1;
71                 baseammo(d->gunselect = gun1);
72                 for(;;)
73                 {
74                     int gun2 = rnd(4)+1;
75                     if(gun1!=gun2) { baseammo(gun2); break; };
76                 };
77             }
78             else if(m_arena)    // insta arena
79             {
80                 d->ammo[GUN_RIFLE] = 100;
81             }
82             else // efficiency
83             {
84                 loopi(4) baseammo(i+1);
85                 d->gunselect = GUN_CG;
86             };
87             d->ammo[GUN_CG] /= 2;
88         };
89     }
90     else
91     {
92         d->ammo[GUN_SG] = 5;
93     };
94 };
95 
newdynent()96 dynent *newdynent()                 // create a new blank player or monster
97 {
98     dynent *d = (dynent *)gp()->alloc(sizeof(dynent));
99     d->o.x = 0;
100     d->o.y = 0;
101     d->o.z = 0;
102     d->yaw = 270;
103     d->pitch = 0;
104     d->roll = 0;
105     d->maxspeed = 22;
106     d->outsidemap = false;
107     d->inwater = false;
108     d->radius = 1.1f;
109     d->eyeheight = 3.2f;
110     d->aboveeye = 0.7f;
111     d->frags = 0;
112     d->plag = 0;
113     d->ping = 0;
114     d->lastupdate = lastmillis;
115     d->enemy = NULL;
116     d->monsterstate = 0;
117     d->name[0] = d->team[0] = 0;
118     d->blocked = false;
119     d->lifesequence = 0;
120     d->state = CS_ALIVE;
121     spawnstate(d);
122     return d;
123 };
124 
respawnself()125 void respawnself()
126 {
127 	spawnplayer(player1);
128 	showscores(false);
129 };
130 
arenacount(dynent * d,int & alive,int & dead,char * & lastteam,bool & oneteam)131 void arenacount(dynent *d, int &alive, int &dead, char *&lastteam, bool &oneteam)
132 {
133     if(d->state!=CS_DEAD)
134     {
135         alive++;
136         if(lastteam && strcmp(lastteam, d->team)) oneteam = false;
137         lastteam = d->team;
138     }
139     else
140     {
141         dead++;
142     };
143 };
144 
145 int arenarespawnwait = 0;
146 int arenadetectwait  = 0;
147 
arenarespawn()148 void arenarespawn()
149 {
150     if(arenarespawnwait)
151     {
152         if(arenarespawnwait<lastmillis)
153         {
154             arenarespawnwait = 0;
155             conoutf("new round starting... fight!");
156             respawnself();
157         };
158     }
159     else if(arenadetectwait==0 || arenadetectwait<lastmillis)
160     {
161         arenadetectwait = 0;
162         int alive = 0, dead = 0;
163         char *lastteam = NULL;
164         bool oneteam = true;
165         loopv(players) if(players[i]) arenacount(players[i], alive, dead, lastteam, oneteam);
166         arenacount(player1, alive, dead, lastteam, oneteam);
167         if(dead>0 && (alive<=1 || (m_teammode && oneteam)))
168         {
169             conoutf("arena round is over! next round in 5 seconds...");
170             if(alive) conoutf("team %s is last man standing", lastteam);
171             else conoutf("everyone died!");
172             arenarespawnwait = lastmillis+5000;
173             arenadetectwait  = lastmillis+10000;
174             player1->roll = 0;
175         };
176     };
177 };
178 
zapdynent(dynent * & d)179 void zapdynent(dynent *&d)
180 {
181     if(d) gp()->dealloc(d, sizeof(dynent));
182     d = NULL;
183 };
184 
185 extern int democlientnum;
186 
otherplayers()187 void otherplayers()
188 {
189     loopv(players) if(players[i])
190     {
191         const int lagtime = lastmillis-players[i]->lastupdate;
192         if(lagtime>1000 && players[i]->state==CS_ALIVE)
193         {
194             players[i]->state = CS_LAGGED;
195             continue;
196         };
197         if(lagtime && players[i]->state != CS_DEAD && (!demoplayback || i!=democlientnum)) moveplayer(players[i], 2, false);   // use physics to extrapolate player position
198     };
199 };
200 
respawn()201 void respawn()
202 {
203     if(player1->state==CS_DEAD)
204     {
205         player1->attacking = false;
206         if(m_arena) { conoutf("waiting for new round to start..."); return; };
207         if(m_sp) { nextmode = gamemode; changemap(clientmap); return; };    // if we die in SP we try the same map again
208 		respawnself();
209 	};
210 };
211 
212 int sleepwait = 0;
213 string sleepcmd;
sleepf(char * msec,char * cmd)214 void sleepf(char *msec, char *cmd) { sleepwait = atoi(msec)+lastmillis; strcpy_s(sleepcmd, cmd); };
215 COMMANDN(sleep, sleepf, ARG_2STR);
216 
updateworld(int millis)217 void updateworld(int millis)        // main game update loop
218 {
219     if(lastmillis)
220     {
221         curtime = millis - lastmillis;
222         if(sleepwait && lastmillis>sleepwait) { sleepwait = 0; execute(sleepcmd); };
223         physicsframe();
224         checkquad(curtime);
225 		if(m_arena) arenarespawn();
226         moveprojectiles((float)curtime);
227         demoplaybackstep();
228         if(!demoplayback)
229         {
230             if(getclientnum()>=0) shoot(player1, worldpos);     // only shoot when connected to server
231             gets2c();           // do this first, so we have most accurate information when our player moves
232         };
233         otherplayers();
234         if(!demoplayback)
235         {
236             monsterthink();
237             if(player1->state==CS_DEAD)
238             {
239 				if(lastmillis-player1->lastaction<2000)
240 				{
241 					player1->move = player1->strafe = 0;
242 					moveplayer(player1, 10, false);
243 				}
244                 else if(!m_arena && !m_sp && lastmillis-player1->lastaction>10000) respawn();
245             }
246             else if(!intermission)
247             {
248                 moveplayer(player1, 20, true);
249                 checkitems();
250             };
251             c2sinfo(player1);   // do this last, to reduce the effective frame lag
252         };
253     };
254     lastmillis = millis;
255 };
256 
entinmap(dynent * d)257 void entinmap(dynent *d)    // brute force but effective way to find a free spawn spot in the map
258 {
259     loopi(100)              // try max 100 times
260     {
261         float dx = (rnd(21)-10)/10.0f*i;  // increasing distance
262         float dy = (rnd(21)-10)/10.0f*i;
263         d->o.x += dx;
264         d->o.y += dy;
265         if(collide(d, true, 0, 0)) return;
266         d->o.x -= dx;
267         d->o.y -= dy;
268     };
269     conoutf("can't find entity spawn spot! (%d, %d)", (int)d->o.x, (int)d->o.y);
270     // leave ent at original pos, possibly stuck
271 };
272 
273 int spawncycle = -1;
274 int fixspawn = 2;
275 
spawnplayer(dynent * d)276 void spawnplayer(dynent *d)   // place at random spawn. also used by monsters!
277 {
278     int r = fixspawn-->0 ? 4 : rnd(10)+1;
279     loopi(r) spawncycle = findentity(PLAYERSTART, spawncycle+1);
280     if(spawncycle!=-1)
281     {
282         d->o.x = ents[spawncycle].x;
283         d->o.y = ents[spawncycle].y;
284         d->o.z = ents[spawncycle].z;
285         d->yaw = ents[spawncycle].attr1;
286         d->pitch = 0;
287         d->roll = 0;
288     }
289     else
290     {
291         d->o.x = d->o.y = (float)ssize/2;
292         d->o.z = 4;
293     };
294     entinmap(d);
295     spawnstate(d);
296     d->state = CS_ALIVE;
297 };
298 
299 // movement input code
300 
301 #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; };
302 
303 dir(backward, move,   -1, k_down,  k_up);
304 dir(forward,  move,    1, k_up,    k_down);
305 dir(left,     strafe,  1, k_left,  k_right);
306 dir(right,    strafe, -1, k_right, k_left);
307 
attack(bool on)308 void attack(bool on)
309 {
310     if(intermission) return;
311     if(editmode) editdrag(on);
312     else if(player1->attacking = on) respawn();
313 };
314 
jumpn(bool on)315 void jumpn(bool on) { if(!intermission && (player1->jumpnext = on)) respawn(); };
316 
317 COMMAND(backward, ARG_DOWN);
318 COMMAND(forward, ARG_DOWN);
319 COMMAND(left, ARG_DOWN);
320 COMMAND(right, ARG_DOWN);
321 COMMANDN(jump, jumpn, ARG_DOWN);
322 COMMAND(attack, ARG_DOWN);
323 COMMAND(showscores, ARG_DOWN);
324 
fixplayer1range()325 void fixplayer1range()
326 {
327     const float MAXPITCH = 90.0f;
328     if(player1->pitch>MAXPITCH) player1->pitch = MAXPITCH;
329     if(player1->pitch<-MAXPITCH) player1->pitch = -MAXPITCH;
330     while(player1->yaw<0.0f) player1->yaw += 360.0f;
331     while(player1->yaw>=360.0f) player1->yaw -= 360.0f;
332 };
333 
mousemove(int dx,int dy)334 void mousemove(int dx, int dy)
335 {
336     if(player1->state==CS_DEAD || intermission) return;
337     const float SENSF = 33.0f;     // try match quake sens
338     player1->yaw += (dx/SENSF)*(sensitivity/(float)sensitivityscale);
339     player1->pitch -= (dy/SENSF)*(sensitivity/(float)sensitivityscale)*(invmouse ? -1 : 1);
340 	fixplayer1range();
341 };
342 
343 // damage arriving from the network, monsters, yourself, all ends up here.
344 
selfdamage(int damage,int actor,dynent * act)345 void selfdamage(int damage, int actor, dynent *act)
346 {
347     if(player1->state!=CS_ALIVE || editmode || intermission) return;
348     damageblend(damage);
349 	demoblend(damage);
350     int ad = damage*(player1->armourtype+1)*20/100;     // let armour absorb when possible
351     if(ad>player1->armour) ad = player1->armour;
352     player1->armour -= ad;
353     damage -= ad;
354     float droll = damage/0.5f;
355     player1->roll += player1->roll>0 ? droll : (player1->roll<0 ? -droll : (rnd(2) ? droll : -droll));  // give player a kick depending on amount of damage
356     if((player1->health -= damage)<=0)
357     {
358         if(actor==-2)
359         {
360             conoutf("you got killed by %s!", act->name);
361         }
362         else if(actor==-1)
363         {
364             actor = getclientnum();
365             conoutf("you suicided!");
366             addmsg(1, 2, SV_FRAGS, --player1->frags);
367         }
368         else
369         {
370             dynent *a = getclient(actor);
371             if(a)
372             {
373                 if(isteam(a->team, player1->team))
374                 {
375                     conoutf("you got fragged by a teammate (%s)", a->name);
376                 }
377                 else
378                 {
379                     conoutf("you got fragged by %s", a->name);
380                 };
381             };
382         };
383         showscores(true);
384         addmsg(1, 2, SV_DIED, actor);
385         player1->lifesequence++;
386         player1->attacking = false;
387         player1->state = CS_DEAD;
388         player1->pitch = 0;
389         player1->roll = 60;
390         playsound(S_DIE1+rnd(2));
391         spawnstate(player1);
392         player1->lastaction = lastmillis;
393     }
394     else
395     {
396         playsound(S_PAIN6);
397     };
398 };
399 
timeupdate(int timeremain)400 void timeupdate(int timeremain)
401 {
402     if(!timeremain)
403     {
404         intermission = true;
405         player1->attacking = false;
406         conoutf("intermission:");
407         conoutf("game has ended!");
408         showscores(true);
409     }
410     else
411     {
412         conoutf("time remaining: %d minutes", timeremain);
413     };
414 };
415 
getclient(int cn)416 dynent *getclient(int cn)   // ensure valid entity
417 {
418     if(cn<0 || cn>=MAXCLIENTS)
419     {
420         neterr("clientnum");
421         return NULL;
422     };
423     while(cn>=players.length()) players.add(NULL);
424     return players[cn] ? players[cn] : (players[cn] = newdynent());
425 };
426 
initclient()427 void initclient()
428 {
429     clientmap[0] = 0;
430     initclientnet();
431 };
432 
startmap(char * name)433 void startmap(char *name)   // called just after a map load
434 {
435     if(netmapstart() && m_sp) { gamemode = 0; conoutf("coop sp not supported yet"); };
436     sleepwait = 0;
437     monsterclear();
438     projreset();
439     spawncycle = -1;
440     spawnplayer(player1);
441     player1->frags = 0;
442     loopv(players) if(players[i]) players[i]->frags = 0;
443     resetspawns();
444     strcpy_s(clientmap, name);
445     if(editmode) toggleedit();
446     setvar("gamespeed", 100);
447 	setvar("fog", 180);
448 	setvar("fogcolour", 0x8099B3);
449     showscores(false);
450     intermission = false;
451     framesinmap = 0;
452     conoutf("game mode is %s", modestr(gamemode));
453 };
454 
455 COMMANDN(map, changemap, ARG_1STR);
456