10 #include "fpsserver.h"
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
);
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
;
48 struct fpsclient
: igameclient
50 // these define classes local to fpsclient
54 #include "scoreboard.h"
55 #include "fpsrender.h"
62 int nextmode
, gamemode
; // nextmode becomes gamemode after next map load
66 int maptime
, minremain
;
70 int respawned
, suicided
;
71 int lastslowmohealth
, slowmorealtimestart
;
72 int lasthit
, lastspawnattempt
;
74 int following
, followdir
;
78 fpsent
*player1
; // our client
79 vector
<fpsent
*> players
; // other clients
80 fpsent lastplayerstate
;
90 IVARP(maxradarscale
, 0, 1024, 10000);
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; }
125 if(player1
->state
!=CS_ALIVE
|| player1
->physstate
<PHYS_SLOPE
) return;
126 if(lastmillis
-player1
->lasttaunt
<1000) return;
127 player1
->lasttaunt
= lastmillis
;
129 extern void tc_taunthook();
132 cc
.addmsg(SV_TAUNT
, "r");
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;
143 conoutf("follow %s", following
>=0 ? "on" : "off");
147 void nextfollow(int dir
)
149 if(player1
->state
!=CS_SPECTATOR
|| players
.empty())
154 int cur
= following
>= 0 ? following
: (dir
< 0 ? players
.length() - 1 : 0);
157 cur
= (cur
+ dir
+ players
.length()) % players
.length();
160 if(following
<0) conoutf("follow on");
169 char *getclientmap() { return clientmap
; }
171 void adddynlights() { ws
.adddynlights(); }
173 void rendergame() { fr
.rendergame(gamemode
); }
175 void resetgamestate()
180 ms
.monsterclear(gamemode
); // all monsters back at their spawns for editing
186 fpsent
*spawnstate(fpsent
*d
) // reset player state not persistent accross spawns
189 d
->spawnstate(gamemode
);
197 if(respawned
!=player1
->lifesequence
)
199 cc
.addmsg(SV_TRYSPAWN
, "r");
200 respawned
= player1
->lifesequence
;
205 spawnplayer(player1
);
206 sb
.showscores(false);
208 if(m_capture
) cpc
.lastrepammo
= -1;
212 fpsent
*pointatplayer()
216 fpsent
*o
= players
[i
];
218 if(intersect(o
, player1
->o
, worldpos
)) return o
;
225 if(following
<0) return;
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
;
241 extern int thirdperson
;
242 if(thirdperson
) return player1
;
243 fpsent
*target
= followingplayer();
244 return target
? target
: player1
;
249 fpsent
*target
= followingplayer();
252 player1
->yaw
= target
->yaw
;
253 player1
->pitch
= target
->state
==CS_DEAD
? 0 : target
->pitch
;
254 player1
->o
= target
->o
;
255 player1
->resetinterp();
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;
283 else if(lagtime
>tc_lagtime
&& d
->state
==CS_ALIVE
)
285 else if(lagtime
>1000 && d
->state
==CS_ALIVE
)
288 d
->state
= CS_LAGGED
;
291 if(d
->state
==CS_ALIVE
|| d
->state
==CS_EDITING
)
293 if(smoothmove() && d
->smoothmillis
>0)
297 d
->pitch
= d
->newpitch
;
298 moveplayer(d
, 1, false);
300 float k
= 1.0f
- float(lastmillis
- d
->smoothmillis
)/smoothmove();
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
);
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
334 maptime
= lm
+ curtime
;
335 extern int totalmillis
;
336 slowmorealtimestart
= totalmillis
;
343 setvar("gamespeed", intermission
? 100 : player1
->health
);
344 if(player1
->health
<player1
->maxhealth
&& lastmillis
-lastslowmohealth
>player1
->health
*player1
->health
/2)
346 lastslowmohealth
= lastmillis
;
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
);
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);
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);
387 d
->state
= cc
.spectator
? CS_SPECTATOR
: (d
==player1
&& editmode
? CS_EDITING
: CS_ALIVE
);
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
);
400 lastspawnattempt
= lastmillis
;
401 //conoutf(CON_GAMEINFO, "\f2you must wait %d second%s before respawn!", wait, wait!=1 ? "s" : "");
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
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);
420 void doattack(bool on
)
422 if(intermission
) return;
423 if((player1
->attacking
= on
)) respawn();
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;
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)
465 d
->lastpain
= lastmillis
;
466 d
->superdamage
= restore
? 0 : max(-d
->health
, 0);
470 setvar("zoom", -1, true);
471 if(!restore
) lastplayerstate
= *player1
;
472 d
->attacking
= false;
473 if(!restore
) d
->deaths
++;
476 playsound(S_DIE1
+rnd(2));
480 d
->move
= d
->strafe
= 0;
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();
496 else if(d
->state
!=CS_ALIVE
|| intermission
) return;
498 fpsent
*h
= followingplayer();
500 int contype
= d
==h
|| actor
==h
? CON_FRAG_SELF
: CON_FRAG_OTHER
;
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
))
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
);
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
);
528 if(d
==player1
) conoutf(contype
, "\f2you got fragged by %s", aname
);
529 else conoutf(contype
, "\f2%s fragged %s", aname
, dname
);
535 void timeupdate(int timeremain
)
537 minremain
= timeremain
;
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
);
549 conoutf(CON_GAMEINFO
, "\f2--- single player time score: ---");
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
);
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
)
581 while(cn
>=players
.length()) players
.add(NULL
);
584 fpsent
*d
= new fpsent();
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;
601 if(followdir
) nextfollow(followdir
);
602 else stopfollowing();
604 fpsent
*d
= players
[cn
];
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
]);
622 void preloadweapons()
626 const char *file
= guns
[i
].file
;
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()
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);
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;
670 if(multiplayer(false) && m_sp
) { gamemode
= 0; conoutf(CON_ERROR
, "coop sp not supported yet"); }
673 ms
.monsterclear(gamemode
);
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);
694 s_strcpy(clientmap
, name
);
695 sb
.showscores(false);
696 setvar("zoom", -1, true);
697 intermission
= false;
699 if(*name
) conoutf(CON_GAMEINFO
, "\f2game mode is %s", fpsserver::modestr(gamemode
));
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())
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
)
734 cc
.addmsg(SV_SOUND
, "i", 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
;
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
];
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;
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
;
767 s_sprintf(cname
)("%s%s \fs\f5(%d)\fr", prefix
, name
, d
->clientnum
);
771 void suicide(physent
*d
)
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;
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
);
800 float swayxy
= sinf(swaymillis
/115.0f
)/100.0f
,
801 swayz
= cosf(swaymillis
/115.0f
)/100.0f
;
802 swap(sway
.x
, sway
.y
);
805 sway
.z
= -fabs(swayspeed
*swayz
);
806 sway
.add(swaydir
).add(d
->o
);
807 if(!hudgunsway()) sway
= d
->o
;
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
;
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
);
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
);
837 drawhudmodel(d
, ANIM_GUNIDLE
|ANIM_LOOP
);
841 void drawicon(float tx
, float ty
, int x
, int y
)
843 settexture("packages/hud/items.png");
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
);
855 float abovegameplayhud()
857 return 1650.0f
/1800.0f
;
861 void quad(int x
, int y
, int xs
, int ys
)
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
);
870 void gameplayhud(int w
, int h
)
872 loopv(tc_hudimages
) {
873 hudimageinfo
*hi
= tc_hudimages
[i
];
874 char *txt
= executeret(hi
->tc_var
);
876 if (hi
->type
[0] == 'i') {
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') {
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
);
897 void gameplayhud(int w
, int h
)
899 if(player1
->state
==CS_SPECTATOR
)
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
);
908 fpsent
*f
= followingplayer();
909 text_bounds(f
? colorname(f
) : " ", fw
, fh
);
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
)
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
);
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
]);
941 glOrtho(0, w
*1800/h
, 1800, 0, -1, 1);
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);
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
);
962 IVARP(teamcrosshair
, 0, 1, 1);
963 IVARP(hitcrosshair
, 0, 425, 1000);
965 const char *defaultcrosshair(int 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;
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
))
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; }
1003 void lighteffects(dynent
*e
, vec
&color
, vec
&dir
)
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
;
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
)
1028 extern void tc_newmaphook(char *name
);
1029 tc_newmaphook("unknown");
1031 cc
.addmsg(SV_NEWMAP
, "ri", size
);
1035 void edittrigger(const selinfo
&sel
, int op
, int arg1
, int arg2
, int arg3
)
1038 extern void tc_edittrigger(const selinfo
&sel
, int op
, int arg1
, int arg2
, int arg3
);
1039 tc_edittrigger(sel
, op
, arg1
, arg2
, arg3
);
1041 if(gamemode
==1) switch(op
)
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
);
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
,
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
,
1074 cc
.addmsg(SV_EDITF
+ op
, "r");
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;
1086 g
->text(names
[i
], 0xFFFF80, !i
? "server" : NULL
);
1091 void serverinfoendcolumn(g3d_gui
*g
, int i
)
1093 g
->mergehits(false);
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
)
1104 if(g
->button(" ", 0xFFFFDD, "server")&G3D_UP
) return true;
1111 if(g
->button(" ", 0xFFFFDD)&G3D_UP
) return true;
1115 if(g
->buttonf("%s ", 0xFFFFDD, NULL
, name
)&G3D_UP
) return true;
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;
1132 if(g
->buttonf("%d ", 0xFFFFDD, "server", ping
)&G3D_UP
) return true;
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;
1144 if(g
->buttonf("%.25s ", 0xFFFFDD, NULL
, map
)&G3D_UP
) return true;
1148 if(g
->buttonf("%s ", 0xFFFFDD, NULL
, attr
.length()>=2 ? fpsserver::modestr(attr
[1], "") : "")&G3D_UP
) return true;
1152 if(g
->buttonf("%s ", 0xFFFFDD, NULL
, attr
.length()>=5 ? fpsserver::mastermodestr(attr
[4], "") : "")&G3D_UP
) return true;
1156 if(g
->buttonf("%s ", 0xFFFFDD, NULL
, name
)&G3D_UP
) return true;
1161 if(g
->buttonf("%.25s", 0xFFFFDD, NULL
, sdesc
)&G3D_UP
) return true;
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());
1185 #include "../remote/fpsplug.cpp"
1190 REGISTERGAME(fpsgame
, "fps", NULL
, new fpsserver());