spelling typo
[SauerbratenRemote.git] / SauerbratenRemote / src / fpsgame / fps.cpp
blobfcc8a2f689ed2495f55c2e174f77ba315ada5109
1 #include "pch.h"
3 #include "cube.h"
5 #include "iengine.h"
6 #include "igame.h"
8 #include "game.h"
10 #include "fpsserver.h"
12 #ifdef TC
13 #include "../remote/tc.h"
14 #include "../remote/fpsplug.h"
16 VAR(peers, 0, 0, 9999);
17 VAR(tc_lagtime, 0, 30000, 999999);
20 vector<hudimageinfo *> tc_hudimages;
22 char *tc_gethudcommand(SDL_Surface *screen, int x, int y) {
23 int w = screen->w, h = screen->h;
24 int x2 = x * screen->w / 1800, y2 = y * screen->h / 1800;
25 loopvrev(tc_hudimages) {
26 hudimageinfo *hi = tc_hudimages[i];
27 char *txt = executeret(hi->tc_var);
28 if (txt && *txt) {
29 if (hi->type[0] == 'i') {
30 int left = (hi->x >= 0) ? hi->x : w + hi->x, top = (hi->y >= 0) ? hi->y : h + hi->y;
31 int width = (hi->w > 0) ? hi->w : w + hi->w, height = (hi->h > 0) ? hi->h : h + hi->h;
32 if (x >= left && x <= left + width && y >= top && y <= top + height) return hi->clicked;
34 } else if (hi->type[0] == 't') {
35 int left = (hi->x >= 0) ? hi->x : w + hi->x, top = (hi->y >= 0) ? hi->y : h + hi->y;
36 int width = (hi->w > 0) ? hi->w : w + hi->w, height = (hi->h > 0) ? hi->h : h + hi->h;
37 if (x2 >= left && x2 <= left + width && y2 >= top && y2 <= top + height) return hi->clicked;
41 return NULL;
44 #endif
46 #ifndef STANDALONE
48 struct fpsclient : igameclient
50 // these define classes local to fpsclient
51 #include "weapon.h"
52 #include "monster.h"
53 #include "movable.h"
54 #include "scoreboard.h"
55 #include "fpsrender.h"
56 #include "entities.h"
57 #include "client.h"
58 #include "capture.h"
59 #include "assassin.h"
60 #include "ctf.h"
62 int nextmode, gamemode; // nextmode becomes gamemode after next map load
63 bool intermission;
64 int lastmillis;
65 string clientmap;
66 int maptime, minremain;
67 int respawnent;
68 int swaymillis;
69 vec swaydir;
70 int respawned, suicided;
71 int lastslowmohealth, slowmorealtimestart;
72 int lasthit, lastspawnattempt;
74 int following, followdir;
76 bool openmainmenu;
78 fpsent *player1; // our client
79 vector<fpsent *> players; // other clients
80 fpsent lastplayerstate;
82 weaponstate ws;
83 monsterset ms;
84 movableset mo;
85 scoreboard sb;
86 fpsrender fr;
87 entities et;
88 clientcom cc;
90 IVARP(maxradarscale, 0, 1024, 10000);
92 captureclient cpc;
93 assassinclient asc;
94 ctfclient ctf;
96 fpsclient()
97 : nextmode(0), gamemode(0), intermission(false), lastmillis(0),
98 maptime(0), minremain(0), respawnent(-1),
99 swaymillis(0), swaydir(0, 0, 0),
100 respawned(-1), suicided(-1),
101 lasthit(0), lastspawnattempt(0),
102 following(-1), followdir(0), openmainmenu(true),
103 player1(spawnstate(new fpsent())),
104 ws(*this), ms(*this), mo(*this), sb(*this), fr(*this), et(*this), cc(*this),
105 cpc(*this), asc(*this), ctf(*this)
107 CCOMMAND(mode, "i", (fpsclient *self, int *val), { self->setmode(*val); });
108 CCOMMAND(kill, "", (fpsclient *self), { self->suicide(self->player1); });
109 CCOMMAND(taunt, "", (fpsclient *self), { self->taunt(); });
110 CCOMMAND(follow, "s", (fpsclient *self, char *s), { self->follow(s); });
111 CCOMMAND(nextfollow, "i", (fpsclient *self, int *dir), { self->nextfollow(*dir < 0 ? -1 : 1); });
114 iclientcom *getcom() { return &cc; }
115 icliententities *getents() { return &et; }
117 void setmode(int mode)
119 if(multiplayer(false) && !m_mp(mode)) { conoutf(CON_ERROR, "mode %d not supported in multiplayer", mode); return; }
120 nextmode = mode;
123 void taunt()
125 if(player1->state!=CS_ALIVE || player1->physstate<PHYS_SLOPE) return;
126 if(lastmillis-player1->lasttaunt<1000) return;
127 player1->lasttaunt = lastmillis;
128 #ifdef TC
129 extern void tc_taunthook();
130 tc_taunthook();
131 #else
132 cc.addmsg(SV_TAUNT, "r");
133 #endif
136 void follow(char *arg)
138 if(arg[0] ? player1->state==CS_SPECTATOR : following>=0)
140 following = arg[0] ? cc.parseplayer(arg) : -1;
141 if(following==player1->clientnum) following = -1;
142 followdir = 0;
143 conoutf("follow %s", following>=0 ? "on" : "off");
147 void nextfollow(int dir)
149 if(player1->state!=CS_SPECTATOR || players.empty())
151 stopfollowing();
152 return;
154 int cur = following >= 0 ? following : (dir < 0 ? players.length() - 1 : 0);
155 loopv(players)
157 cur = (cur + dir + players.length()) % players.length();
158 if(players[cur])
160 if(following<0) conoutf("follow on");
161 following = cur;
162 followdir = dir;
163 return;
166 stopfollowing();
169 char *getclientmap() { return clientmap; }
171 void adddynlights() { ws.adddynlights(); }
173 void rendergame() { fr.rendergame(gamemode); }
175 void resetgamestate()
177 if(m_classicsp)
179 mo.clear(gamemode);
180 ms.monsterclear(gamemode); // all monsters back at their spawns for editing
181 resettriggers();
183 ws.projreset();
186 fpsent *spawnstate(fpsent *d) // reset player state not persistent accross spawns
188 d->respawn();
189 d->spawnstate(gamemode);
190 return d;
193 void respawnself()
195 if(m_mp(gamemode))
197 if(respawned!=player1->lifesequence)
199 cc.addmsg(SV_TRYSPAWN, "r");
200 respawned = player1->lifesequence;
203 else
205 spawnplayer(player1);
206 sb.showscores(false);
207 lasthit = 0;
208 if(m_capture) cpc.lastrepammo = -1;
212 fpsent *pointatplayer()
214 loopv(players)
216 fpsent *o = players[i];
217 if(!o) continue;
218 if(intersect(o, player1->o, worldpos)) return o;
220 return NULL;
223 void stopfollowing()
225 if(following<0) return;
226 following = -1;
227 followdir = 0;
228 conoutf("follow off");
231 fpsent *followingplayer()
233 if(player1->state!=CS_SPECTATOR || following<0) return NULL;
234 fpsent *target = getclient(following);
235 if(target && target->state!=CS_SPECTATOR) return target;
236 return NULL;
239 fpsent *hudplayer()
241 extern int thirdperson;
242 if(thirdperson) return player1;
243 fpsent *target = followingplayer();
244 return target ? target : player1;
247 void setupcamera()
249 fpsent *target = followingplayer();
250 if(target)
252 player1->yaw = target->yaw;
253 player1->pitch = target->state==CS_DEAD ? 0 : target->pitch;
254 player1->o = target->o;
255 player1->resetinterp();
259 bool detachcamera()
261 fpsent *d = hudplayer();
262 return d->state==CS_DEAD;
265 IVARP(smoothmove, 0, 75, 100);
266 IVARP(smoothdist, 0, 32, 64);
268 void otherplayers(int curtime)
270 loopv(players) if(players[i])
272 fpsent *d = players[i];
274 if(d->state==CS_ALIVE)
276 if(lastmillis - d->lastaction >= d->gunwait) d->gunwait = 0;
277 if(d->quadmillis) et.checkquad(curtime, d);
280 const int lagtime = lastmillis-d->lastupdate;
281 if(!lagtime || intermission) continue;
282 #ifdef TC
283 else if(lagtime>tc_lagtime && d->state==CS_ALIVE)
284 #else
285 else if(lagtime>1000 && d->state==CS_ALIVE)
286 #endif
288 d->state = CS_LAGGED;
289 continue;
291 if(d->state==CS_ALIVE || d->state==CS_EDITING)
293 if(smoothmove() && d->smoothmillis>0)
295 d->o = d->newpos;
296 d->yaw = d->newyaw;
297 d->pitch = d->newpitch;
298 moveplayer(d, 1, false);
299 d->newpos = d->o;
300 float k = 1.0f - float(lastmillis - d->smoothmillis)/smoothmove();
301 if(k>0)
303 d->o.add(vec(d->deltapos).mul(k));
304 d->yaw += d->deltayaw*k;
305 if(d->yaw<0) d->yaw += 360;
306 else if(d->yaw>=360) d->yaw -= 360;
307 d->pitch += d->deltapitch*k;
310 else moveplayer(d, 1, false);
312 else if(d->state==CS_DEAD && lastmillis-d->lastpain<2000) moveplayer(d, 1, true);
316 void addsway(int curtime)
318 fpsent *d = hudplayer();
319 if(d->state!=CS_SPECTATOR)
321 if(d->physstate>=PHYS_SLOPE) swaymillis += curtime;
322 float k = pow(0.7f, curtime/10.0f);
323 swaydir.mul(k);
324 vec vel(d->vel);
325 vel.add(d->falling);
326 swaydir.add(vec(vel).mul((1-k)/(15*max(vel.magnitude(), d->maxspeed))));
330 void updateworld(vec &pos, int curtime, int lm) // main game update loop
332 if(!maptime)
334 maptime = lm + curtime;
335 extern int totalmillis;
336 slowmorealtimestart = totalmillis;
337 return;
339 lastmillis = lm;
341 if(m_slowmo)
343 setvar("gamespeed", intermission ? 100 : player1->health);
344 if(player1->health<player1->maxhealth && lastmillis-lastslowmohealth>player1->health*player1->health/2)
346 lastslowmohealth = lastmillis;
347 player1->health++;
351 if(!curtime) return;
353 physicsframe();
354 et.checkquad(curtime, player1);
355 ws.moveprojectiles(curtime);
356 if(player1->clientnum>=0 && player1->state==CS_ALIVE) ws.shoot(player1, pos); // only shoot when connected to server
357 ws.bounceupdate(curtime); // need to do this after the player shoots so grenades don't end up inside player's BB next frame
358 otherplayers(curtime);
359 gets2c();
360 mo.update(curtime);
361 ms.monsterthink(curtime, gamemode);
362 if(player1->state==CS_DEAD)
364 if(lastmillis-player1->lastpain<2000)
366 player1->move = player1->strafe = 0;
367 moveplayer(player1, 10, false);
369 if(m_assassin && asc.respawnwait()<=0) respawnself();
371 else if(!intermission)
373 moveplayer(player1, 10, true);
374 addsway(curtime);
375 et.checkitems(player1);
376 if(m_classicsp) checktriggers();
377 else if(m_capture) cpc.checkbaseammo(player1);
378 else if(m_ctf) ctf.checkflags(player1);
380 if(player1->clientnum>=0) c2sinfo(player1); // do this last, to reduce the effective frame lag
383 void spawnplayer(fpsent *d) // place at random spawn. also used by monsters!
385 findplayerspawn(d, m_capture ? cpc.pickspawn(d->team) : (respawnent>=0 ? respawnent : -1), m_ctf ? ctfteamflag(player1->team) : 0);
386 spawnstate(d);
387 d->state = cc.spectator ? CS_SPECTATOR : (d==player1 && editmode ? CS_EDITING : CS_ALIVE);
390 void respawn()
392 if(player1->state==CS_DEAD)
394 player1->attacking = false;
395 if(m_capture || m_ctf)
397 int wait = m_capture ? cpc.respawnwait(player1) : ctf.respawnwait(player1);
398 if(wait>0)
400 lastspawnattempt = lastmillis;
401 //conoutf(CON_GAMEINFO, "\f2you must wait %d second%s before respawn!", wait, wait!=1 ? "s" : "");
402 return;
405 if(m_arena) { conoutf(CON_GAMEINFO, "\f2waiting for new round to start..."); return; }
406 if(m_dmsp) { nextmode = gamemode; cc.changemap(clientmap); return; } // if we die in SP we try the same map again
407 if(m_classicsp)
409 respawnself();
410 conoutf(CON_GAMEINFO, "\f2You wasted another life! The monsters stole your armour and some ammo...");
411 loopi(NUMGUNS) if(i!=GUN_PISTOL && (player1->ammo[i] = lastplayerstate.ammo[i])>5) player1->ammo[i] = max(player1->ammo[i]/3, 5);
412 return;
414 respawnself();
418 // inputs
420 void doattack(bool on)
422 if(intermission) return;
423 if((player1->attacking = on)) respawn();
426 bool canjump()
428 if(!intermission) respawn();
429 return player1->state!=CS_DEAD && !intermission;
432 bool allowmove(physent *d)
434 if(d->type!=ENT_PLAYER) return true;
435 return !((fpsent *)d)->lasttaunt || lastmillis-((fpsent *)d)->lasttaunt>=1000;
438 void damaged(int damage, fpsent *d, fpsent *actor, bool local = true)
440 if(d->state!=CS_ALIVE || intermission) return;
442 fpsent *h = local ? player1 : hudplayer();
443 if(actor==h && d!=actor) lasthit = lastmillis;
445 if(local) damage = d->dodamage(damage);
446 else if(actor==player1) return;
448 if(d==h)
450 damageblend(damage);
451 damagecompass(damage, actor->o);
452 d->damageroll(damage);
453 if(m_slowmo && player1->health<1) player1->health = 1;
455 ws.damageeffect(damage, d, d!=h);
457 if(d->health<=0) { if(local) killed(d, actor); }
458 else if(d==player1) playsound(S_PAIN6);
459 else playsound(S_PAIN1+rnd(5), &d->o);
462 void deathstate(fpsent *d, bool restore = false)
464 d->state = CS_DEAD;
465 d->lastpain = lastmillis;
466 d->superdamage = restore ? 0 : max(-d->health, 0);
467 if(d==player1)
469 sb.showscores(true);
470 setvar("zoom", -1, true);
471 if(!restore) lastplayerstate = *player1;
472 d->attacking = false;
473 if(!restore) d->deaths++;
474 d->pitch = 0;
475 d->roll = 0;
476 playsound(S_DIE1+rnd(2));
478 else
480 d->move = d->strafe = 0;
481 d->resetinterp();
482 playsound(S_DIE1+rnd(2), &d->o);
483 if(!restore) ws.superdamageeffect(d->vel, d);
487 void killed(fpsent *d, fpsent *actor)
489 if(d->state==CS_EDITING)
491 d->editstate = CS_DEAD;
492 if(d==player1) d->deaths++;
493 else d->resetinterp();
494 return;
496 else if(d->state!=CS_ALIVE || intermission) return;
498 fpsent *h = followingplayer();
499 if(!h) h = player1;
500 int contype = d==h || actor==h ? CON_FRAG_SELF : CON_FRAG_OTHER;
501 string dname, aname;
502 s_strcpy(dname, d==player1 ? "you" : colorname(d));
503 s_strcpy(aname, actor==player1 ? "you" : (actor->type!=ENT_INANIMATE ? colorname(actor) : ""));
504 if(actor->type==ENT_AI)
505 conoutf(contype, "\f2%s got killed by %s!", dname, aname);
506 else if(d==actor || actor->type==ENT_INANIMATE)
507 conoutf(contype, "\f2%s suicided%s", dname, d==player1 ? "!" : "");
508 else if(isteam(d->team, actor->team))
510 if(d==player1) conoutf(contype, "\f2you got fragged by a teammate (%s)", aname);
511 else conoutf(contype, "\f2%s fragged a teammate (%s)", aname, dname);
513 else if(m_assassin && (d==player1 || actor==player1))
515 if(d==player1)
517 conoutf(contype, "\f2you got fragged by %s (%s)", aname, asc.hunters.find(actor)>=0 ? "assassin" : (asc.targets.find(actor)>=0 ? "target" : "friend"));
518 if(asc.hunters.find(actor)>=0) asc.hunters.removeobj(actor);
520 else
522 conoutf(contype, "\f2you fragged %s (%s)", dname, asc.targets.find(d)>=0 ? "target +1" : (asc.hunters.find(d)>=0 ? "assassin +0" : "friend -1"));
523 if(asc.targets.find(d)>=0) asc.targets.removeobj(d);
526 else
528 if(d==player1) conoutf(contype, "\f2you got fragged by %s", aname);
529 else conoutf(contype, "\f2%s fragged %s", aname, dname);
532 deathstate(d);
535 void timeupdate(int timeremain)
537 minremain = timeremain;
538 if(!timeremain)
540 intermission = true;
541 player1->attacking = false;
542 conoutf(CON_GAMEINFO, "\f2intermission:");
543 conoutf(CON_GAMEINFO, "\f2game has ended!");
544 conoutf(CON_GAMEINFO, "\f2player frags: %d, deaths: %d", player1->frags, player1->deaths);
545 int accuracy = player1->totaldamage*100/max(player1->totalshots, 1);
546 conoutf(CON_GAMEINFO, "\f2player total damage dealt: %d, damage wasted: %d, accuracy(%%): %d", player1->totaldamage, player1->totalshots-player1->totaldamage, accuracy);
547 if(m_sp)
549 conoutf(CON_GAMEINFO, "\f2--- single player time score: ---");
550 int pen, score = 0;
551 extern int totalmillis;
552 pen = (totalmillis-slowmorealtimestart)/1000; score += pen; if(pen) conoutf(CON_GAMEINFO, "\f2time taken: %d seconds (%d simulated seconds)", pen, (lastmillis-maptime)/1000);
553 pen = player1->deaths*60; score += pen; if(pen) conoutf(CON_GAMEINFO, "\f2time penalty for %d deaths (1 minute each): %d seconds", player1->deaths, pen);
554 pen = ms.remain*10; score += pen; if(pen) conoutf(CON_GAMEINFO, "\f2time penalty for %d monsters remaining (10 seconds each): %d seconds", ms.remain, pen);
555 pen = (10-ms.skill())*20; score += pen; if(pen) conoutf(CON_GAMEINFO, "\f2time penalty for lower skill level (20 seconds each): %d seconds", pen);
556 pen = 100-accuracy; score += pen; if(pen) conoutf(CON_GAMEINFO, "\f2time penalty for missed shots (1 second each %%): %d seconds", pen);
557 s_sprintfd(aname)("bestscore_%s", getclientmap());
558 const char *bestsc = getalias(aname);
559 int bestscore = *bestsc ? atoi(bestsc) : score;
560 if(score<bestscore) bestscore = score;
561 s_sprintfd(nscore)("%d", bestscore);
562 alias(aname, nscore);
563 conoutf(CON_GAMEINFO, "\f2TOTAL SCORE (time + time penalties): %d seconds (best so far: %d seconds)", score, bestscore);
565 sb.showscores(true);
566 setvar("zoom", -1, true);
568 else if(timeremain > 0)
570 conoutf(CON_GAMEINFO, "\f2time remaining: %d %s", timeremain, timeremain==1 ? "minute" : "minutes");
574 fpsent *newclient(int cn) // ensure valid entity
576 if(cn<0 || cn>=MAXCLIENTS)
578 neterr("clientnum");
579 return NULL;
581 while(cn>=players.length()) players.add(NULL);
582 if(!players[cn])
584 fpsent *d = new fpsent();
585 d->clientnum = cn;
586 players[cn] = d;
588 return players[cn];
591 fpsent *getclient(int cn) // ensure valid entity
593 return players.inrange(cn) ? players[cn] : NULL;
596 void clientdisconnected(int cn, bool notify = true)
598 if(!players.inrange(cn)) return;
599 if(following==cn)
601 if(followdir) nextfollow(followdir);
602 else stopfollowing();
604 fpsent *d = players[cn];
605 if(!d) return;
606 if(notify && d->name[0]) conoutf("player %s disconnected", colorname(d));
607 ws.removebouncers(d);
608 ws.removeprojectiles(d);
609 removetrackedparticles(d);
610 if(m_assassin) asc.removeplayer(d);
611 else if(m_ctf) ctf.removeplayer(d);
612 DELETEP(players[cn]);
613 cleardynentcache();
616 void initclient()
618 clientmap[0] = 0;
619 cc.initclientnet();
622 void preloadweapons()
624 loopi(NUMGUNS)
626 const char *file = guns[i].file;
627 if(!file) continue;
628 s_sprintfd(mdl)("hudguns/%s", file);
629 loadmodel(mdl, -1, true);
630 s_sprintf(mdl)("hudguns/%s/blue", file);
631 loadmodel(mdl, -1, true);
632 s_sprintf(mdl)("vwep/%s", file);
633 loadmodel(mdl, -1, true);
637 void preloadbouncers()
639 const char *mdls[] =
641 "gibc", "gibh",
642 "projectiles/grenade", "projectiles/rocket",
643 "debris/debris01", "debris/debris02", "debris/debris03", "debris/debris04",
644 "barreldebris/debris01", "barreldebris/debris02", "barreldebris/debris03", "barreldebris/debris04"
646 loopi(sizeof(mdls)/sizeof(mdls[0]))
648 loadmodel(mdls[i], -1, true);
652 void preload()
654 preloadweapons();
655 preloadbouncers();
656 fr.preloadplayermodel();
657 et.preloadentities();
658 if(m_sp) ms.preloadmonsters();
659 else if(m_capture) cpc.preloadbases();
660 else if(m_ctf) ctf.preloadflags();
663 IVARP(startmenu, 0, 1, 1);
665 void startmap(const char *name) // called just after a map load
667 respawned = suicided = -1;
668 respawnent = -1;
669 lasthit = 0;
670 if(multiplayer(false) && m_sp) { gamemode = 0; conoutf(CON_ERROR, "coop sp not supported yet"); }
671 cc.mapstart();
672 mo.clear(gamemode);
673 ms.monsterclear(gamemode);
674 ws.projreset();
676 // reset perma-state
677 player1->frags = 0;
678 player1->deaths = 0;
679 player1->totaldamage = 0;
680 player1->totalshots = 0;
681 player1->maxhealth = 100;
682 loopv(players) if(players[i])
684 players[i]->frags = 0;
685 players[i]->deaths = 0;
686 players[i]->totaldamage = 0;
687 players[i]->totalshots = 0;
688 players[i]->maxhealth = 100;
691 if(!m_mp(gamemode)) spawnplayer(player1);
692 else findplayerspawn(player1, -1);
693 et.resetspawns();
694 s_strcpy(clientmap, name);
695 sb.showscores(false);
696 setvar("zoom", -1, true);
697 intermission = false;
698 maptime = 0;
699 if(*name) conoutf(CON_GAMEINFO, "\f2game mode is %s", fpsserver::modestr(gamemode));
700 if(m_sp)
702 s_sprintfd(aname)("bestscore_%s", getclientmap());
703 const char *best = getalias(aname);
704 if(*best) conoutf(CON_GAMEINFO, "\f2try to beat your best score so far: %s", best);
705 lastslowmohealth = lastmillis;
708 if(*name && openmainmenu && startmenu())
710 #ifdef TC
711 showgui("Plexus");
712 #else
713 showgui("main");
714 #endif
715 openmainmenu = false;
718 if(identexists("mapstart")) execute("mapstart");
721 void physicstrigger(physent *d, bool local, int floorlevel, int waterlevel, int material)
723 if(d->type==ENT_INANIMATE) return;
724 if (waterlevel>0) { if(material!=MAT_LAVA) playsound(S_SPLASH1, d==player1 ? NULL : &d->o); }
725 else if(waterlevel<0) playsound(material==MAT_LAVA ? S_BURN : S_SPLASH2, d==player1 ? NULL : &d->o);
726 if (floorlevel>0) { if(d==player1 || d->type!=ENT_PLAYER) playsoundc(S_JUMP, (fpsent *)d); }
727 else if(floorlevel<0) { if(d==player1 || d->type!=ENT_PLAYER) playsoundc(S_LAND, (fpsent *)d); }
730 void playsoundc(int n, fpsent *d = NULL)
732 if(!d || d==player1)
734 cc.addmsg(SV_SOUND, "i", n);
735 playsound(n);
737 else playsound(n, &d->o);
740 int numdynents() { return 1+players.length()+ms.monsters.length()+mo.movables.length(); }
742 dynent *iterdynents(int i)
744 if(!i) return player1;
745 i--;
746 if(i<players.length()) return players[i];
747 i -= players.length();
748 if(i<ms.monsters.length()) return ms.monsters[i];
749 i -= ms.monsters.length();
750 if(i<mo.movables.length()) return mo.movables[i];
751 return NULL;
754 bool duplicatename(fpsent *d, char *name = NULL)
756 if(!name) name = d->name;
757 if(d!=player1 && !strcmp(name, player1->name)) return true;
758 loopv(players) if(players[i] && d!=players[i] && !strcmp(name, players[i]->name)) return true;
759 return false;
762 char *colorname(fpsent *d, char *name = NULL, const char *prefix = "")
764 if(!name) name = d->name;
765 if(name[0] && !duplicatename(d, name)) return name;
766 static string cname;
767 s_sprintf(cname)("%s%s \fs\f5(%d)\fr", prefix, name, d->clientnum);
768 return cname;
771 void suicide(physent *d)
773 if(d==player1)
775 if(d->state!=CS_ALIVE) return;
776 if(!m_mp(gamemode)) killed(player1, player1);
777 else if(suicided!=player1->lifesequence)
779 cc.addmsg(SV_SUICIDE, "r");
780 suicided = player1->lifesequence;
783 else if(d->type==ENT_AI) ((monsterset::monster *)d)->monsterpain(400, player1);
784 else if(d->type==ENT_INANIMATE) ((movableset::movable *)d)->suicide();
787 IVARP(hudgun, 0, 1, 1);
788 IVARP(hudgunsway, 0, 1, 1);
789 IVARP(teamhudguns, 0, 1, 1);
791 void drawhudmodel(fpsent *d, int anim, float speed = 0, int base = 0)
793 if(d->gunselect>GUN_PISTOL) return;
795 vec sway;
796 vecfromyawpitch(d->yaw, d->pitch, 1, 0, sway);
797 float swayspeed = sqrtf(d->vel.x*d->vel.x + d->vel.y*d->vel.y);
798 swayspeed = min(4.0f, swayspeed);
799 sway.mul(swayspeed);
800 float swayxy = sinf(swaymillis/115.0f)/100.0f,
801 swayz = cosf(swaymillis/115.0f)/100.0f;
802 swap(sway.x, sway.y);
803 sway.x *= -swayxy;
804 sway.y *= swayxy;
805 sway.z = -fabs(swayspeed*swayz);
806 sway.add(swaydir).add(d->o);
807 if(!hudgunsway()) sway = d->o;
809 #if 0
810 if(player1->state!=CS_DEAD && player1->quadmillis)
812 float t = 0.5f + 0.5f*sinf(2*M_PI*lastmillis/1000.0f);
813 color.y = color.y*(1-t) + t;
815 #endif
817 s_sprintfd(gunname)("hudguns/%s", guns[d->gunselect].file);
818 if((m_teamskins || fr.teamskins()) && teamhudguns())
819 s_strcat(gunname, d==player1 || isteam(d->team, player1->team) ? "/blue" : "/red");
820 rendermodel(NULL, gunname, anim, sway, d->yaw+90, d->pitch, MDL_LIGHT, NULL, NULL, base, speed);
823 void drawhudgun()
825 if(!hudgun() || editmode) return;
827 fpsent *d = hudplayer();
828 if(d->state==CS_SPECTATOR || d->state==CS_EDITING) return;
830 int rtime = ws.reloadtime(d->gunselect);
831 if(d->lastaction && d->lastattackgun==d->gunselect && lastmillis-d->lastaction<rtime)
833 drawhudmodel(d, ANIM_GUNSHOOT, rtime/17.0f, d->lastaction);
835 else
837 drawhudmodel(d, ANIM_GUNIDLE|ANIM_LOOP);
841 void drawicon(float tx, float ty, int x, int y)
843 settexture("packages/hud/items.png");
844 glBegin(GL_QUADS);
845 tx /= 384;
846 ty /= 128;
847 int s = 120;
848 glTexCoord2f(tx, ty); glVertex2f(x, y);
849 glTexCoord2f(tx+1/6.0f, ty); glVertex2f(x+s, y);
850 glTexCoord2f(tx+1/6.0f, ty+1/2.0f); glVertex2f(x+s, y+s);
851 glTexCoord2f(tx, ty+1/2.0f); glVertex2f(x, y+s);
852 glEnd();
855 float abovegameplayhud()
857 return 1650.0f/1800.0f;
860 #ifdef TC
861 void quad(int x, int y, int xs, int ys)
863 glBegin(GL_QUADS);
864 glTexCoord2f(0, 0); glVertex2i(x, y);
865 glTexCoord2f(1, 0); glVertex2i(x+xs, y);
866 glTexCoord2f(1, 1); glVertex2i(x+xs, y+ys);
867 glTexCoord2f(0, 1); glVertex2i(x, y+ys);
868 glEnd();
870 void gameplayhud(int w, int h)
872 loopv(tc_hudimages) {
873 hudimageinfo *hi = tc_hudimages[i];
874 char *txt = executeret(hi->tc_var);
875 if (txt && *txt) {
876 if (hi->type[0] == 'i') {
877 glLoadIdentity();
878 glOrtho(0, w, h, 0, -1, 1);
879 settexture(txt, true);
880 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
881 int left = (hi->x >= 0) ? hi->x : w + hi->x, top = (hi->y >= 0) ? hi->y : h + hi->y;
882 int width = (hi->w > 0) ? hi->w : w + hi->w, height = (hi->h > 0) ? hi->h : h + hi->h;
883 quad(left, top, width, height);
884 //printf("texture is '%s', l: %d t: %d w: %d h: %d\n", txt, left, top, width, height);
885 } else if (hi->type[0] == 't') {
886 glLoadIdentity();
887 int left = (hi->x >= 0) ? hi->x : w + hi->x, top = (hi->y >= 0) ? hi->y : h + hi->y;
888 int width = (hi->w > 0) ? hi->w : w + hi->w, height = (hi->h > 0) ? hi->h : h + hi->h;
889 glOrtho(0, width*1800/height, 1800, 0, -1, 1);
890 draw_textf(txt, left, top);
896 #else
897 void gameplayhud(int w, int h)
899 if(player1->state==CS_SPECTATOR)
901 glLoadIdentity();
902 glOrtho(0, w*1800/h, 1800, 0, -1, 1);
904 int pw, ph, tw, th, fw, fh;
905 text_bounds(" ", pw, ph);
906 text_bounds("SPECTATOR", tw, th);
907 th = max(th, ph);
908 fpsent *f = followingplayer();
909 text_bounds(f ? colorname(f) : " ", fw, fh);
910 fh = max(fh, ph);
911 draw_text("SPECTATOR", w*1800/h - tw - pw, 1650 - th - fh);
912 if(f) draw_text(colorname(f), w*1800/h - fw - pw, 1650 - fh);
915 fpsent *d = hudplayer();
916 if(d->state==CS_EDITING) return;
918 if(d->state==CS_SPECTATOR)
920 if(m_capture || m_ctf)
922 glLoadIdentity();
923 glOrtho(0, w*1800/h, 1800, 0, -1, 1);
924 if(m_capture) cpc.capturehud(d, w, h);
925 else if(m_ctf) ctf.drawhud(d, w, h);
927 return;
930 glLoadIdentity();
931 glOrtho(0, w*900/h, 900, 0, -1, 1);
933 draw_textf("%d", 90, 822, d->state==CS_DEAD ? 0 : d->health);
934 if(d->state!=CS_DEAD)
936 if(d->armour) draw_textf("%d", 390, 822, d->armour);
937 draw_textf("%d", 690, 822, d->ammo[d->gunselect]);
940 glLoadIdentity();
941 glOrtho(0, w*1800/h, 1800, 0, -1, 1);
943 glDisable(GL_BLEND);
945 drawicon(192, 0, 20, 1650);
946 if(d->state!=CS_DEAD)
948 if(d->armour) drawicon((float)(d->armourtype*64), 0, 620, 1650);
949 int g = d->gunselect, r = 64;
950 if(g==GUN_PISTOL) { g = 4; r = 0; }
951 drawicon((float)(g*64), (float)r, 1220, 1650);
954 glEnable(GL_BLEND);
956 if(m_capture) cpc.capturehud(d, w, h);
957 else if(m_ctf) ctf.drawhud(d, w, h);
958 else if(m_assassin && d==player1) asc.drawhud(w, h);
960 #endif
962 IVARP(teamcrosshair, 0, 1, 1);
963 IVARP(hitcrosshair, 0, 425, 1000);
965 const char *defaultcrosshair(int index)
967 switch(index)
969 case 2: return "data/hit.png";
970 case 1: return "data/teammate.png";
971 default: return "data/crosshair.png";
975 int selectcrosshair(float &r, float &g, float &b)
977 fpsent *d = hudplayer();
978 if(d->state==CS_SPECTATOR || d->state==CS_DEAD) return -1;
980 if(d->state!=CS_ALIVE) return 0;
982 int crosshair = 0;
983 if(lasthit && lastmillis - lasthit < hitcrosshair()) crosshair = 2;
984 else if(teamcrosshair())
986 dynent *o = ws.intersectclosest(d->o, worldpos, d);
987 if(o && o->type==ENT_PLAYER && isteam(((fpsent *)o)->team, d->team))
989 crosshair = 1;
990 r = g = 0;
994 if(d->gunwait) { r *= 0.5f; g *= 0.5f; b *= 0.5f; }
995 else if(!crosshair && r && g && b && !editmode && !m_noitemsrail)
997 if(d->health<=25) { r = 1.0f; g = b = 0; }
998 else if(d->health<=50) { r = 1.0f; g = 0.5f; b = 0; }
1000 return crosshair;
1003 void lighteffects(dynent *e, vec &color, vec &dir)
1005 #if 0
1006 fpsent *d = (fpsent *)e;
1007 if(d->state!=CS_DEAD && d->quadmillis)
1009 float t = 0.5f + 0.5f*sinf(2*M_PI*lastmillis/1000.0f);
1010 color.y = color.y*(1-t) + t;
1012 #endif
1015 void particletrack(physent *owner, vec &o, vec &d)
1017 if(owner->type!=ENT_PLAYER && owner->type!=ENT_AI) return;
1018 float dist = o.dist(d);
1019 vecfromyawpitch(owner->yaw, owner->pitch, 1, 0, d);
1020 float newdist = raycube(owner->o, d, dist, RAY_CLIPMAT|RAY_ALPHAPOLY);
1021 d.mul(min(newdist, dist)).add(owner->o);
1022 o = ws.hudgunorigin(GUN_PISTOL, owner->o, d, (fpsent *)owner);
1025 void newmap(int size)
1027 #ifdef TC
1028 extern void tc_newmaphook(char *name);
1029 tc_newmaphook("unknown");
1030 #else
1031 cc.addmsg(SV_NEWMAP, "ri", size);
1032 #endif
1035 void edittrigger(const selinfo &sel, int op, int arg1, int arg2, int arg3)
1037 #ifdef TC
1038 extern void tc_edittrigger(const selinfo &sel, int op, int arg1, int arg2, int arg3);
1039 tc_edittrigger(sel, op, arg1, arg2, arg3);
1040 #else
1041 if(gamemode==1) switch(op)
1043 case EDIT_FLIP:
1044 case EDIT_COPY:
1045 case EDIT_PASTE:
1046 case EDIT_DELCUBE:
1048 cc.addmsg(SV_EDITF + op, "ri9i4",
1049 sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient,
1050 sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner);
1051 break;
1053 case EDIT_MAT:
1054 case EDIT_ROTATE:
1056 cc.addmsg(SV_EDITF + op, "ri9i5",
1057 sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient,
1058 sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner,
1059 arg1);
1060 break;
1062 case EDIT_FACE:
1063 case EDIT_TEX:
1064 case EDIT_REPLACE:
1066 cc.addmsg(SV_EDITF + op, "ri9i6",
1067 sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient,
1068 sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner,
1069 arg1, arg2);
1070 break;
1072 case EDIT_REMIP:
1074 cc.addmsg(SV_EDITF + op, "r");
1075 break;
1078 #endif
1081 bool serverinfostartcolumn(g3d_gui *g, int i)
1083 static const char *names[] = { "ping ", "players ", "map ", "mode ", "master ", "host ", "description " };
1084 if(size_t(i) >= sizeof(names)/sizeof(names[0])) return false;
1085 g->pushlist();
1086 g->text(names[i], 0xFFFF80, !i ? "server" : NULL);
1087 g->mergehits(true);
1088 return true;
1091 void serverinfoendcolumn(g3d_gui *g, int i)
1093 g->mergehits(false);
1094 g->poplist();
1097 bool serverinfoentry(g3d_gui *g, int i, const char *name, const char *sdesc, const char *map, int ping, const vector<int> &attr, int np)
1099 if(ping < 0 || attr.empty() || attr[0]!=PROTOCOL_VERSION)
1101 switch(i)
1103 case 0:
1104 if(g->button(" ", 0xFFFFDD, "server")&G3D_UP) return true;
1105 break;
1107 case 1:
1108 case 2:
1109 case 3:
1110 case 4:
1111 if(g->button(" ", 0xFFFFDD)&G3D_UP) return true;
1112 break;
1114 case 5:
1115 if(g->buttonf("%s ", 0xFFFFDD, NULL, name)&G3D_UP) return true;
1116 break;
1118 case 6:
1119 if(ping < 0)
1121 if(g->button(sdesc, 0xFFFFDD)&G3D_UP) return true;
1123 else if(g->buttonf("[%s protocol] ", 0xFFFFDD, NULL, attr.empty() ? "unknown" : (attr[0] < PROTOCOL_VERSION ? "older" : "newer"))&G3D_UP) return true;
1124 break;
1126 return false;
1129 switch(i)
1131 case 0:
1132 if(g->buttonf("%d ", 0xFFFFDD, "server", ping)&G3D_UP) return true;
1133 break;
1135 case 1:
1136 if(attr.length()>=4)
1138 if(g->buttonf("%d/%d ", 0xFFFFDD, NULL, np, attr[3])&G3D_UP) return true;
1140 else if(g->buttonf("%d ", 0xFFFFDD, NULL, np)&G3D_UP) return true;
1141 break;
1143 case 2:
1144 if(g->buttonf("%.25s ", 0xFFFFDD, NULL, map)&G3D_UP) return true;
1145 break;
1147 case 3:
1148 if(g->buttonf("%s ", 0xFFFFDD, NULL, attr.length()>=2 ? fpsserver::modestr(attr[1], "") : "")&G3D_UP) return true;
1149 break;
1151 case 4:
1152 if(g->buttonf("%s ", 0xFFFFDD, NULL, attr.length()>=5 ? fpsserver::mastermodestr(attr[4], "") : "")&G3D_UP) return true;
1153 break;
1155 case 5:
1156 if(g->buttonf("%s ", 0xFFFFDD, NULL, name)&G3D_UP) return true;
1157 break;
1159 case 6:
1161 if(g->buttonf("%.25s", 0xFFFFDD, NULL, sdesc)&G3D_UP) return true;
1162 break;
1165 return false;
1168 void g3d_gamemenus() { sb.show(); }
1170 // any data written into this vector will get saved with the map data. Must take care to do own versioning, and endianess if applicable. Will not get called when loading maps from other games, so provide defaults.
1171 void writegamedata(vector<char> &extras) {}
1172 void readgamedata(vector<char> &extras) {}
1174 const char *gameident() { return "fps"; }
1175 const char *defaultmap() { return "metl4"; }
1176 const char *savedconfig() { return "config.cfg"; }
1177 const char *defaultconfig() { return "data/defaults.cfg"; }
1178 const char *autoexec() { return "autoexec.cfg"; }
1179 const char *savedservers() { return "servers.cfg"; }
1182 REGISTERGAME(fpsgame, "fps", new fpsclient(), new fpsserver());
1184 #ifdef TC
1185 #include "../remote/fpsplug.cpp"
1186 #endif
1188 #else
1190 REGISTERGAME(fpsgame, "fps", NULL, new fpsserver());
1192 #endif